From 1df67989349fdcabb4cb8570e455ba9625aba0ce Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 11 Apr 2018 22:19:38 +0200 Subject: [PATCH 001/138] Fix issues in RPC extension. --- Build/MQTTnet.Extensions.Rpc.nuspec | 32 +++++++++++-------- Build/MQTTnet.nuspec | 20 ++---------- .../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 30 ++++++----------- .../MQTTnet.Extensions.Rpc/SampleCCode.c | 29 ++++++++++++++++- .../MqttApplicationMessageBuilder.cs | 9 ++++-- .../Packets/MqttUnsubscribe.cs | 2 +- MQTTnet.sln | 1 + .../MainPage.xaml.cs | 5 +-- 8 files changed, 70 insertions(+), 58 deletions(-) diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec index ec09c1de..2f2fe9b7 100644 --- a/Build/MQTTnet.Extensions.Rpc.nuspec +++ b/Build/MQTTnet.Extensions.Rpc.nuspec @@ -2,7 +2,7 @@ MQTTnet.Extensions.Rpc - 2.6.0 + 2.7.4 Christian Kratky Christian Kratky https://github.com/chkr1011/MQTTnet/blob/master/LICENSE @@ -12,36 +12,42 @@ This is a extension library which allows executing synchronous device calls including a response using MQTTnet. * Initial version. - Copyright Christian Kratky 2016-2017 + Copyright Christian Kratky 2016-2018 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 - + - + - - + - - + - - + - - + + + + - + + + + + + + + \ No newline at end of file diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 2b7bf1e6..cb859b16 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -2,7 +2,7 @@ MQTTnet - 2.7.4 + 2.7.5 Christian Kratky Christian Kratky https://github.com/chkr1011/MQTTnet/blob/master/LICENSE @@ -10,47 +10,34 @@ https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png false MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). - * [Client] Fixed a deadlock while the client disconnects. -* [Client] Fixed broken support for protocol version 3.1.0. -* [Server] The _MqttTcpServerAdapter_ is now added to the ASP.NET services. -* [Server] _MqttServerAdapter_ is renamed to _MqttTcpServerAdapter_ (BREAKING CHANGE!). -* [Server] The server no longer sends the will message of a client if the disconnect was clean (via _Disconnect_ packet). -* [Server] The application message interceptor now allows closing the connection. -* [Server] Added a new flag for the _ClientDisconnected_ event which contains a value indicating whether the disconnect was clean (via _Disconnect_ packet). + * [Client] Added new overloads for the message builder. Copyright Christian Kratky 2016-2018 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 - - - - - - - @@ -62,7 +49,6 @@ - - + \ No newline at end of file diff --git a/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index 938fcb4c..78836ea4 100644 --- a/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Text; using System.Threading.Tasks; using MQTTnet.Client; using MQTTnet.Internal; @@ -9,11 +10,9 @@ namespace MQTTnet.Extensions.Rpc { public sealed class MqttRpcClient : IDisposable { - private const string ResponseTopic = "$MQTTnet.RPC/+/+/response"; private readonly ConcurrentDictionary> _waitingCalls = new ConcurrentDictionary>(); private readonly IMqttClient _mqttClient; - private bool _isEnabled; - + public MqttRpcClient(IMqttClient mqttClient) { _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); @@ -21,16 +20,9 @@ namespace MQTTnet.Extensions.Rpc _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; } - public async Task EnableAsync() + public Task ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) { - await _mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(ResponseTopic).WithAtLeastOnceQoS().Build()); - _isEnabled = true; - } - - public async Task DisableAsync() - { - await _mqttClient.UnsubscribeAsync(ResponseTopic); - _isEnabled = false; + return ExecuteAsync(timeout, methodName, Encoding.UTF8.GetBytes(payload), qualityOfServiceLevel); } public async Task ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel) @@ -42,12 +34,7 @@ namespace MQTTnet.Extensions.Rpc throw new ArgumentException("The method name cannot contain /, + or #."); } - if (!_isEnabled) - { - throw new InvalidOperationException("The RPC client is not enabled."); - } - - var requestTopic = $"$MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}"; + var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}"; var responseTopic = requestTopic + "/response"; var requestMessage = new MqttApplicationMessageBuilder() @@ -64,18 +51,21 @@ namespace MQTTnet.Extensions.Rpc throw new InvalidOperationException(); } - await _mqttClient.PublishAsync(requestMessage); + await _mqttClient.SubscribeAsync(responseTopic, qualityOfServiceLevel).ConfigureAwait(false); + await _mqttClient.PublishAsync(requestMessage).ConfigureAwait(false); + return await tcs.Task.TimeoutAfter(timeout); } finally { _waitingCalls.TryRemove(responseTopic, out _); + await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false); } } private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs) { - if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out TaskCompletionSource tcs)) + if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var tcs)) { return; } diff --git a/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c b/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c index 5f282702..1afa32ce 100644 --- a/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c +++ b/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c @@ -1 +1,28 @@ - \ No newline at end of file +// If using the MQTT client PubSubClient it must be ensured that the request topic for each method is subscribed like the following. +_mqttClient.subscribe("MQTTnet.RPC/+/ping"); +_mqttClient.subscribe("MQTTnet.RPC/+/do_something"); + +// It is not allowed to change the structure of the topic. Otherwise RPC will not work. So method names can be separated using +// an _ or . but no +, # or /.. If it is required to distinguish between devices own rules can be defined like the following. +_mqttClient.subscribe("MQTTnet.RPC/+/deviceA.ping"); +_mqttClient.subscribe("MQTTnet.RPC/+/deviceB.ping"); +_mqttClient.subscribe("MQTTnet.RPC/+/deviceC.getTemperature"); + +// Within the callback of the mqtt client the topic must be checked if it belongs to MQTTnet RPC. The following code shows one +// possible way. +void mqtt_Callback(char *topic, byte *payload, unsigned int payloadLength) +{ + String topicString = String(topic); + + if (topicString.startsWith("MQTTnet.RPC/")) { + String responseTopic = topicString + String("/response"); + + if (topicString.endsWith("/deviceA.ping")) { + mqtt_publish(responseTopic, "pong", false); + return; + } + } +} + +// Do not send response message with the _retain_ flag set to true. +// All required data for a RPC call and the result must be placed into the payload. \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageBuilder.cs b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageBuilder.cs index ce949b85..79c65ece 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageBuilder.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageBuilder.cs @@ -32,7 +32,12 @@ namespace MQTTnet return this; } - public MqttApplicationMessageBuilder WithPayload(MemoryStream payload) + public MqttApplicationMessageBuilder WithPayload(Stream payload) + { + return WithPayload(payload, payload.Length - payload.Position); + } + + public MqttApplicationMessageBuilder WithPayload(Stream payload, long length) { if (payload == null) { @@ -46,7 +51,7 @@ namespace MQTTnet } else { - _payload = new byte[payload.Length - payload.Position]; + _payload = new byte[length]; payload.Read(_payload, 0, _payload.Length); } diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs index 6c24a477..a4d84535 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs @@ -11,7 +11,7 @@ namespace MQTTnet.Packets public override string ToString() { var topicFiltersText = string.Join(",", TopicFilters); - return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; + return "Unsubscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; } } } diff --git a/MQTTnet.sln b/MQTTnet.sln index cece21c5..fab76088 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1 appveyor.yml = appveyor.yml Build\build.ps1 = Build\build.ps1 Build\MQTTnet.AspNetCore.nuspec = Build\MQTTnet.AspNetCore.nuspec + Build\MQTTnet.Extensions.Rpc.nuspec = Build\MQTTnet.Extensions.Rpc.nuspec Build\MQTTnet.nuspec = Build\MQTTnet.nuspec EndProjectSection EndProject diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index da0c2f31..d907f1d8 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -350,14 +350,11 @@ namespace MQTTnet.TestApp.UniversalWindows payload = Convert.FromBase64String(RpcPayload.Text); } - try { var rpcClient = new MqttRpcClient(_mqttClient); - await rpcClient.EnableAsync(); var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), RpcMethod.Text, payload, qos); - await rpcClient.DisableAsync(); - + RpcResponses.Items.Add(RpcMethod.Text + " >>> " + Encoding.UTF8.GetString(response)); } catch (MqttCommunicationTimedOutException) From a5f33bc244fe2ecdb71136f3e7869b049076fa23 Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Fri, 13 Apr 2018 23:00:36 -0300 Subject: [PATCH 002/138] Replace double dictionary with a tuple key --- .../Adapter/MqttChannelAdapter.cs | 6 ++- .../Client/MqttPacketDispatcher.cs | 38 +++++++++---------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index c2f9ba43..123b88d3 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -115,9 +115,13 @@ namespace MQTTnet.Adapter ReceivedMqttPacket receivedMqttPacket = null; try { + if (timeout > TimeSpan.Zero) { - receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); + var timeoutCts = new CancellationTokenSource(timeout); + var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); + + receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, linkedCts.Token).ConfigureAwait(false); } else { diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs index 8b09fbce..172e53bd 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs @@ -10,7 +10,8 @@ namespace MQTTnet.Client { public class MqttPacketDispatcher { - private readonly ConcurrentDictionary>> _awaiters = new ConcurrentDictionary>>(); + + private readonly ConcurrentDictionary, TaskCompletionSource> _awaiters = new ConcurrentDictionary, TaskCompletionSource>(); private readonly IMqttNetLogger _logger; public MqttPacketDispatcher(IMqttNetLogger logger) @@ -22,7 +23,7 @@ namespace MQTTnet.Client { var packetAwaiter = AddPacketAwaiter(responseType, identifier); try - { + { return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false); } catch (MqttCommunicationTimedOutException) @@ -40,21 +41,20 @@ namespace MQTTnet.Client { if (packet == null) throw new ArgumentNullException(nameof(packet)); - var type = packet.GetType(); - - if (_awaiters.TryGetValue(type, out var byId)) + ushort? identifier = 0; + if (packet is IMqttPacketWithIdentifier packetWithIdentifier) { - ushort? identifier = 0; - if (packet is IMqttPacketWithIdentifier packetWithIdentifier) - { - identifier = packetWithIdentifier.PacketIdentifier; - } + identifier = packetWithIdentifier.PacketIdentifier; + } - if (byId.TryRemove(identifier.Value, out var tcs)) - { - tcs.TrySetResult(packet); - return; - } + var type = packet.GetType(); + var key = new Tuple(identifier, type); + + + if (_awaiters.TryRemove(key, out var tcs)) + { + tcs.TrySetResult(packet); + return; } throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched."); @@ -74,8 +74,8 @@ namespace MQTTnet.Client identifier = 0; } - var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary>()); - if (!byId.TryAdd(identifier.Value, tcs)) + var dictionaryKey = new Tuple(identifier, responseType); + if (!_awaiters.TryAdd(dictionaryKey,tcs)) { throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}."); } @@ -90,8 +90,8 @@ namespace MQTTnet.Client identifier = 0; } - var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary>()); - byId.TryRemove(identifier.Value, out var _); + var dictionaryKey = new Tuple(identifier, responseType); + _awaiters.TryRemove(dictionaryKey, out var _); } } } \ No newline at end of file From d2049d63d633abbb5bacc8fc4be7b605db97613b Mon Sep 17 00:00:00 2001 From: Christian Date: Sun, 15 Apr 2018 21:07:41 +0200 Subject: [PATCH 003/138] Refactoring minor issues --- .../Server/MqttClientPendingMessagesQueue.cs | 1 - .../MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs | 2 +- .../Server/MqttClientSubscriptionsManager.cs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs index 99493874..9e7ebd72 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index 8dd9d062..4698f7cf 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -187,7 +187,7 @@ namespace MQTTnet.Server { foreach (var clientSession in _sessions.Values) { - await clientSession.EnqueueApplicationMessageAsync(applicationMessage); + await clientSession.EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false); } } finally diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs index e4ee9214..93182174 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs @@ -10,8 +10,8 @@ namespace MQTTnet.Server { public sealed class MqttClientSubscriptionsManager : IDisposable { - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly Dictionary _subscriptions = new Dictionary(); + private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly IMqttServerOptions _options; private readonly string _clientId; From c915af8dada22d92b186a2413bc96341a319aeb1 Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Mon, 16 Apr 2018 12:30:56 -0300 Subject: [PATCH 004/138] Disable Nagle on sockets, send packets in one shot. --- .../Adapter/MqttChannelAdapter.cs | 58 ++++++++----------- .../Implementations/MqttTcpChannel.cs | 3 + .../Implementations/MqttTcpServerAdapter.cs | 4 +- .../Serializer/IMqttPacketSerializer.cs | 2 +- .../Serializer/MqttPacketSerializer.cs | 29 ++++++---- .../Serializer/MqttPacketWriter.cs | 15 +++-- 6 files changed, 60 insertions(+), 51 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 123b88d3..8adac690 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading; @@ -55,52 +56,41 @@ namespace MQTTnet.Adapter return ExecuteAndWrapExceptionAsync(async () => { - await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try + foreach (var packet in packets) { - foreach (var packet in packets) - { - if (cancellationToken.IsCancellationRequested) - { - return; - } - - if (packet == null) - { - continue; - } - - _logger.Verbose("TX >>> {0} [Timeout={1}]", packet, timeout); - - var chunks = PacketSerializer.Serialize(packet); - foreach (var chunk in chunks) - { - if (cancellationToken.IsCancellationRequested) - { - return; - } - - await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false); - } - } - if (cancellationToken.IsCancellationRequested) { return; } - if (timeout > TimeSpan.Zero) + if (packet == null) { - await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); + continue; } - else + + _logger.Verbose("TX >>> {0} [Timeout={1}]", packet, timeout); + + var packetData = PacketSerializer.Serialize(packet); + if (cancellationToken.IsCancellationRequested) { - await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false); + return; } + await _channel.SendStream.WriteAsync(packetData.Array, packetData.Offset, (int)packetData.Count, cancellationToken).ConfigureAwait(false); + } - finally + + if (cancellationToken.IsCancellationRequested) { - _semaphore.Release(); + return; + } + + if (timeout > TimeSpan.Zero) + { + await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); + } + else + { + await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false); } }); } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs index 8d43d878..85fe31dd 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs @@ -60,6 +60,7 @@ namespace MQTTnet.Implementations if (_socket == null) { _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + } #if NET452 || NET461 @@ -68,6 +69,8 @@ namespace MQTTnet.Implementations await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false); #endif + _socket.NoDelay = true; + if (_options.TlsOptions.UseTls) { _sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs index dc33a3f2..8f896d5b 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs @@ -39,6 +39,8 @@ namespace MQTTnet.Implementations if (options.DefaultEndpointOptions.IsEnabled) { _defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); + _defaultEndpointSocket.NoDelay = true; + _defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort())); _defaultEndpointSocket.Listen(options.ConnectionBacklog); @@ -102,7 +104,7 @@ namespace MQTTnet.Implementations #else var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false); #endif - + clientSocket.NoDelay=true; var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs index 6577b0ad..6afdd984 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs @@ -9,7 +9,7 @@ namespace MQTTnet.Serializer { MqttProtocolVersion ProtocolVersion { get; set; } - ICollection> Serialize(MqttBasePacket mqttPacket); + ArraySegment Serialize(MqttBasePacket mqttPacket); MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body); } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 0904c759..75e72395 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -16,29 +16,36 @@ namespace MQTTnet.Serializer public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; - public ICollection> Serialize(MqttBasePacket packet) + public ArraySegment Serialize(MqttBasePacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); using (var stream = new MemoryStream(128)) using (var writer = new MqttPacketWriter(stream)) { - var fixedHeader = SerializePacket(packet, writer); - var remainingLength = (int)stream.Length; - writer.Write(fixedHeader); - MqttPacketWriter.WriteRemainingLength(remainingLength, writer); - var headerLength = (int)stream.Length - remainingLength; + //leave enough head space for max header (fixed + 4 variable remaining lenght) + stream.Position = 5; + var fixedHeader = SerializePacket(packet, writer); + + var remainingLength = MqttPacketWriter.GetRemainingLength((int)stream.Length-5); + + var headerSize = remainingLength.Length + 1; + var headerOffset = 5 - headerSize; + + //position curson on correct offset on beginining of array + stream.Position = headerOffset; + + //write header + writer.Write(fixedHeader); + writer.Write(remainingLength,0,remainingLength.Length); + #if NET461 || NET452 || NETSTANDARD2_0 var buffer = stream.GetBuffer(); #else var buffer = stream.ToArray(); #endif - return new List> - { - new ArraySegment(buffer, remainingLength, headerLength), - new ArraySegment(buffer, 0, remainingLength) - }; + return new ArraySegment(buffer, headerOffset, (int)stream.Length- headerOffset); } } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs index cb3d4589..794a4ee9 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Text; using MQTTnet.Protocol; @@ -55,14 +56,16 @@ namespace MQTTnet.Serializer Write(value); } - public static void WriteRemainingLength(int length, BinaryWriter target) + public static byte[] GetRemainingLength(int length) { if (length == 0) { - target.Write((byte)0); - return; + return new byte[] { (byte)0 }; } + var bytes = new byte[4]; + int arraySize = 0; + // Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html. var x = length; do @@ -74,8 +77,12 @@ namespace MQTTnet.Serializer encodedByte = encodedByte | 128; } - target.Write((byte)encodedByte); + bytes[arraySize] = (byte)encodedByte; + + arraySize++; } while (x > 0); + + return bytes.Take(arraySize).ToArray(); } } } From 9bc34e44065550f8a433186df7cc7c2a58e57596 Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Mon, 16 Apr 2018 13:07:19 -0300 Subject: [PATCH 005/138] Translate timeout exception --- .../Adapter/MqttChannelAdapter.cs | 19 ++++++++++++++++++- .../MqttCommunicationTimedOutException.cs | 7 ++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 8adac690..e853d6b9 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Sockets; +using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -111,7 +112,23 @@ namespace MQTTnet.Adapter var timeoutCts = new CancellationTokenSource(timeout); var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); - receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, linkedCts.Token).ConfigureAwait(false); + try + { + receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, linkedCts.Token).ConfigureAwait(false); + } + catch(OperationCanceledException ex) + { + //check if timed out + if(linkedCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + { + //only timeout token was cancelled + throw new MqttCommunicationTimedOutException(ex); + } + else + { + throw; + } + } } else { diff --git a/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs index 7d0adcd4..744035ef 100644 --- a/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs +++ b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs @@ -1,6 +1,11 @@ -namespace MQTTnet.Exceptions +using System; + +namespace MQTTnet.Exceptions { public sealed class MqttCommunicationTimedOutException : MqttCommunicationException { + public MqttCommunicationTimedOutException() : base() { } + public MqttCommunicationTimedOutException(Exception innerException) : base(innerException) { } + } } From 786fa53334aef6a47a1e9bcbef764528383b3057 Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Mon, 16 Apr 2018 13:07:35 -0300 Subject: [PATCH 006/138] Fix unit tests for serializer --- .../MqttPacketSerializerTests.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 1111d907..a7421c6d 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -403,9 +403,9 @@ namespace MQTTnet.Core.Tests private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) { var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion }; - var chunks = serializer.Serialize(packet); + var data = serializer.Serialize(packet); - Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(chunks))); + Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(data))); } private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) @@ -438,5 +438,16 @@ namespace MQTTnet.Core.Tests return buffer.ToArray(); } + + private static byte[] Join(params ArraySegment[] chunks) + { + var buffer = new MemoryStream(); + foreach (var chunk in chunks) + { + buffer.Write(chunk.Array, chunk.Offset, chunk.Count); + } + + return buffer.ToArray(); + } } } From e006377e64b93d5b777d9c5a4e75eadd308adeba Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Mon, 16 Apr 2018 13:25:56 -0300 Subject: [PATCH 007/138] Fix zero lenght packets bug --- Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs index 794a4ee9..e7cfa4cf 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs @@ -58,9 +58,9 @@ namespace MQTTnet.Serializer public static byte[] GetRemainingLength(int length) { - if (length == 0) + if (length <= 0) { - return new byte[] { (byte)0 }; + return new [] { (byte)0 }; } var bytes = new byte[4]; From 11ba46d140d0674cee4949eeb6125e777fa08773 Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Mon, 16 Apr 2018 14:01:07 -0300 Subject: [PATCH 008/138] Remove useless stream flush --- .../Adapter/MqttChannelAdapter.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index e853d6b9..d84a8a18 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -76,7 +76,11 @@ namespace MQTTnet.Adapter { return; } - await _channel.SendStream.WriteAsync(packetData.Array, packetData.Offset, (int)packetData.Count, cancellationToken).ConfigureAwait(false); + await _channel.SendStream.WriteAsync( + packetData.Array, + packetData.Offset, + (int)packetData.Count, + cancellationToken).ConfigureAwait(false); } @@ -85,14 +89,6 @@ namespace MQTTnet.Adapter return; } - if (timeout > TimeSpan.Zero) - { - await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); - } - else - { - await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false); - } }); } From cd09a2f1760a35c32f672797423755d5513be112 Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Mon, 16 Apr 2018 14:08:04 -0300 Subject: [PATCH 009/138] Fix code style --- Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs | 5 +---- .../Exceptions/MqttCommunicationTimedOutException.cs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index d84a8a18..787231e9 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -84,10 +84,7 @@ namespace MQTTnet.Adapter } - if (cancellationToken.IsCancellationRequested) - { - return; - } + }); } diff --git a/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs index 744035ef..86f58b38 100644 --- a/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs +++ b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs @@ -4,7 +4,7 @@ namespace MQTTnet.Exceptions { public sealed class MqttCommunicationTimedOutException : MqttCommunicationException { - public MqttCommunicationTimedOutException() : base() { } + public MqttCommunicationTimedOutException() { } public MqttCommunicationTimedOutException(Exception innerException) : base(innerException) { } } From 53007425435113bdc17f348862d40ba4c53e20c7 Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Mon, 16 Apr 2018 14:30:52 -0300 Subject: [PATCH 010/138] Avoid unnecessary enumeration for every packet --- .../Adapter/IMqttChannelAdapter.cs | 2 +- .../Adapter/MqttChannelAdapter.cs | 56 ++++++++++--------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs index 7a5c02a2..8f03e65e 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs @@ -15,7 +15,7 @@ namespace MQTTnet.Adapter Task DisconnectAsync(TimeSpan timeout); - Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable packets); + Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets); Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken); } diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 787231e9..7fffa169 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -51,40 +51,44 @@ namespace MQTTnet.Adapter return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout)); } - public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable packets) + public async Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets) + { + for(var i=0;i { - foreach (var packet in packets) + if (cancellationToken.IsCancellationRequested) { - if (cancellationToken.IsCancellationRequested) - { - return; - } - - if (packet == null) - { - continue; - } - - _logger.Verbose("TX >>> {0} [Timeout={1}]", packet, timeout); - - var packetData = PacketSerializer.Serialize(packet); - if (cancellationToken.IsCancellationRequested) - { - return; - } - await _channel.SendStream.WriteAsync( - packetData.Array, - packetData.Offset, - (int)packetData.Count, - cancellationToken).ConfigureAwait(false); - + return; } + - + _logger.Verbose("TX >>> {0} [Timeout={1}]", packet, timeout); + + var packetData = PacketSerializer.Serialize(packet); + if (cancellationToken.IsCancellationRequested) + { + return; + } + await _channel.SendStream.WriteAsync( + packetData.Array, + packetData.Offset, + (int)packetData.Count, + cancellationToken).ConfigureAwait(false); }); } From a62c15ad1a096caf16d64e3fe22a6dbd1174d71e Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Mon, 16 Apr 2018 14:46:46 -0300 Subject: [PATCH 011/138] Missing interface implementation --- Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index a898f765..f564e51a 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -31,7 +31,7 @@ namespace MQTTnet.Core.Tests return Task.FromResult(0); } - public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable packets) + public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets) { ThrowIfPartnerIsNull(); From 41df5cdd8b8a0194600cda86b813f7b9032acb99 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 17 Apr 2018 20:16:00 +0200 Subject: [PATCH 012/138] Update docs --- Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c | 11 ++++++----- README.md | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c b/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c index 1afa32ce..c20aa4af 100644 --- a/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c +++ b/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c @@ -3,13 +3,13 @@ _mqttClient.subscribe("MQTTnet.RPC/+/ping"); _mqttClient.subscribe("MQTTnet.RPC/+/do_something"); // It is not allowed to change the structure of the topic. Otherwise RPC will not work. So method names can be separated using -// an _ or . but no +, # or /.. If it is required to distinguish between devices own rules can be defined like the following. +// an _ or . but no +, # or . If it is required to distinguish between devices own rules can be defined like the following. _mqttClient.subscribe("MQTTnet.RPC/+/deviceA.ping"); _mqttClient.subscribe("MQTTnet.RPC/+/deviceB.ping"); _mqttClient.subscribe("MQTTnet.RPC/+/deviceC.getTemperature"); -// Within the callback of the mqtt client the topic must be checked if it belongs to MQTTnet RPC. The following code shows one -// possible way. +// Within the callback of the MQTT client the topic must be checked if it belongs to MQTTnet RPC. The following code shows one +// possible way of doing this. void mqtt_Callback(char *topic, byte *payload, unsigned int payloadLength) { String topicString = String(topic); @@ -24,5 +24,6 @@ void mqtt_Callback(char *topic, byte *payload, unsigned int payloadLength) } } -// Do not send response message with the _retain_ flag set to true. -// All required data for a RPC call and the result must be placed into the payload. \ No newline at end of file +// Important notes: +// ! Do not send response message with the _retain_ flag set to true. +// ! All required data for a RPC call and the result must be placed into the payload. \ No newline at end of file diff --git a/README.md b/README.md index 6a3111d8..83a03a35 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * Performance optimized (processing ~40.000 messages / second)* * Interfaces included for mocking and testing * Access to internal trace messages -* Unit tested (70+ tests) +* Unit tested (~80 tests) \* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_. From ae11e31048d6178ba66449fa82952bbd0ac9fe2d Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 17 Apr 2018 20:37:56 +0200 Subject: [PATCH 013/138] Refactoring --- Build/MQTTnet.nuspec | 1 + .../Adapter/MqttChannelAdapter.cs | 25 ++++++++----------- .../Implementations/MqttTcpChannel.cs | 7 +++--- .../Implementations/MqttTcpServerAdapter.cs | 10 ++++---- .../Serializer/MqttPacketSerializer.cs | 18 ++++++------- .../Serializer/MqttPacketWriter.cs | 10 ++++---- .../PerformanceTest.cs | 4 +-- 7 files changed, 35 insertions(+), 40 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index cb859b16..8a26bd40 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -11,6 +11,7 @@ false MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). * [Client] Added new overloads for the message builder. +* [Core] Performance optimizations (thanks to @ israellot) Copyright Christian Kratky 2016-2018 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 diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 7fffa169..087b1042 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net.Sockets; -using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -53,14 +50,13 @@ namespace MQTTnet.Adapter public async Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets) { - for(var i=0;i("TX >>> {0} [Timeout={1}]", packet, timeout); var packetData = PacketSerializer.Serialize(packet); + if (cancellationToken.IsCancellationRequested) { return; } + await _channel.SendStream.WriteAsync( packetData.Array, packetData.Offset, - (int)packetData.Count, + packetData.Count, cancellationToken).ConfigureAwait(false); + await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false); }); } @@ -113,12 +111,11 @@ namespace MQTTnet.Adapter { receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, linkedCts.Token).ConfigureAwait(false); } - catch(OperationCanceledException ex) + catch (OperationCanceledException ex) { - //check if timed out - if(linkedCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + var timedOut = linkedCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested; + if (timedOut) { - //only timeout token was cancelled throw new MqttCommunicationTimedOutException(ex); } else diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs index 85fe31dd..0f9bba48 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs @@ -14,7 +14,7 @@ namespace MQTTnet.Implementations { public sealed class MqttTcpChannel : IMqttChannel { -#if NET452 || NET461 +#if NET452 || NET461 || NETSTANDARD2_0 // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user. @@ -60,7 +60,6 @@ namespace MQTTnet.Implementations if (_socket == null) { _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); - } #if NET452 || NET461 @@ -212,8 +211,8 @@ namespace MQTTnet.Implementations { stream = new NetworkStream(_socket, true); } - -#if NET452 || NET461 + +#if NET452 || NET461 || NETSTANDARD2_0 SendStream = new BufferedStream(stream, _bufferSize); ReceiveStream = new BufferedStream(stream, _bufferSize); #else diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs index 8f896d5b..b718dbbd 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs @@ -38,8 +38,7 @@ namespace MQTTnet.Implementations if (options.DefaultEndpointOptions.IsEnabled) { - _defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); - _defaultEndpointSocket.NoDelay = true; + _defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; _defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort())); _defaultEndpointSocket.Listen(options.ConnectionBacklog); @@ -80,7 +79,7 @@ namespace MQTTnet.Implementations _defaultEndpointSocket = null; _tlsCertificate = null; - + _tlsEndpointSocket?.Dispose(); _tlsEndpointSocket = null; @@ -104,7 +103,8 @@ namespace MQTTnet.Implementations #else var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false); #endif - clientSocket.NoDelay=true; + clientSocket.NoDelay = true; + var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } @@ -139,7 +139,7 @@ namespace MQTTnet.Implementations var sslStream = new SslStream(new NetworkStream(clientSocket)); await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); - + var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 75e72395..2447e05b 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -2,7 +2,6 @@ using MQTTnet.Packets; using MQTTnet.Protocol; using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -23,29 +22,28 @@ namespace MQTTnet.Serializer using (var stream = new MemoryStream(128)) using (var writer = new MqttPacketWriter(stream)) { - //leave enough head space for max header (fixed + 4 variable remaining lenght) + // Leave enough head space for max header size (fixed + 4 variable remaining length) stream.Position = 5; var fixedHeader = SerializePacket(packet, writer); - var remainingLength = MqttPacketWriter.GetRemainingLength((int)stream.Length-5); + var remainingLength = MqttPacketWriter.GetRemainingLength((int)stream.Length - 5); var headerSize = remainingLength.Length + 1; var headerOffset = 5 - headerSize; - //position curson on correct offset on beginining of array + // Position cursor on correct offset on beginining of array (has leading 0x0) stream.Position = headerOffset; - - //write header + writer.Write(fixedHeader); - writer.Write(remainingLength,0,remainingLength.Length); - + writer.Write(remainingLength, 0, remainingLength.Length); + #if NET461 || NET452 || NETSTANDARD2_0 var buffer = stream.GetBuffer(); #else var buffer = stream.ToArray(); #endif - return new ArraySegment(buffer, headerOffset, (int)stream.Length- headerOffset); + return new ArraySegment(buffer, headerOffset, (int)stream.Length - headerOffset); } } @@ -300,7 +298,7 @@ namespace MQTTnet.Serializer return packet; } - + private static void ValidateConnectPacket(MqttConnectPacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs index e7cfa4cf..253294b1 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs @@ -60,11 +60,11 @@ namespace MQTTnet.Serializer { if (length <= 0) { - return new [] { (byte)0 }; + return new[] { (byte)0 }; } var bytes = new byte[4]; - int arraySize = 0; + var offset = 0; // Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html. var x = length; @@ -77,12 +77,12 @@ namespace MQTTnet.Serializer encodedByte = encodedByte | 128; } - bytes[arraySize] = (byte)encodedByte; + bytes[offset] = (byte)encodedByte; - arraySize++; + offset++; } while (x > 0); - return bytes.Take(arraySize).ToArray(); + return bytes.Take(offset).ToArray(); } } } diff --git a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index 5f2e5adc..b485533a 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -44,7 +44,7 @@ namespace MQTTnet.TestApp.NetCore try { - await client.ConnectAsync(options); + await client.ConnectAsync(options).ConfigureAwait(false); } catch (Exception exception) { @@ -165,7 +165,7 @@ namespace MQTTnet.TestApp.NetCore Console.WriteLine("Press any key to exit."); Console.ReadLine(); - await mqttServer.StopAsync(); + await mqttServer.StopAsync().ConfigureAwait(false); } catch (Exception e) { From 17b74f20c9967ee692c40641d8b3eefaa1c77cf2 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 17 Apr 2018 22:29:23 +0200 Subject: [PATCH 014/138] Migrate to Stream-less approach. --- .../MqttWebSocketServerChannel.cs | 24 ++- .../Adapter/IMqttChannelAdapter.cs | 3 +- .../Adapter/MqttChannelAdapter.cs | 57 +++----- .../Channel/IMqttChannel.cs | 10 +- .../MQTTnet.NetStandard/Client/MqttClient.cs | 9 +- .../Client/MqttClientTcpOptions.cs | 2 +- .../Implementations/MqttTcpChannel.Uwp.cs | 44 +++--- .../Implementations/MqttTcpChannel.cs | 101 ++++--------- .../MqttTcpServerAdapter.Uwp.cs | 2 + .../Implementations/MqttTcpServerAdapter.cs | 4 +- .../Implementations/MqttWebSocketChannel.cs | 108 ++++++++++++-- .../Implementations/WebSocketStream.cs | 137 ------------------ .../Serializer/IMqttPacketSerializer.cs | 3 +- .../Serializer/MqttPacketReader.cs | 18 +-- .../Serializer/MqttPacketSerializer.cs | 12 +- .../Serializer/MqttPacketWriter.cs | 5 +- README.md | 2 +- .../MqttPacketReaderTests.cs | 3 +- .../MqttPacketSerializerTests.cs | 2 +- Tests/MQTTnet.Core.Tests/TestMqttChannel.cs | 41 ++++++ .../TestMqttCommunicationAdapter.cs | 3 +- .../PerformanceTest.cs | 2 +- 22 files changed, 260 insertions(+), 332 deletions(-) delete mode 100644 Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs create mode 100644 Tests/MQTTnet.Core.Tests/TestMqttChannel.cs diff --git a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs index 7325a5f8..82bd915c 100644 --- a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs +++ b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs @@ -8,22 +8,24 @@ using MQTTnet.Implementations; namespace MQTTnet.AspNetCore { - public class MqttWebSocketServerChannel : IMqttChannel, IDisposable + public class MqttWebSocketServerChannel : IMqttChannel { private WebSocket _webSocket; + private readonly MqttWebSocketChannel _channel; public MqttWebSocketServerChannel(WebSocket webSocket) { _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); - SendStream = new WebSocketStream(_webSocket); + + _channel = new MqttWebSocketChannel(webSocket); ReceiveStream = SendStream; } - public Stream SendStream { get; private set; } - public Stream ReceiveStream { get; private set; } + private Stream SendStream { get; set; } + private Stream ReceiveStream { get; set; } - public Task ConnectAsync() + public Task ConnectAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } @@ -37,7 +39,7 @@ namespace MQTTnet.AspNetCore try { - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); + await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false); } finally { @@ -45,6 +47,16 @@ namespace MQTTnet.AspNetCore } } + public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return ReceiveStream.ReadAsync(buffer, offset, count, cancellationToken); + } + + public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return SendStream.WriteAsync(buffer, offset, count, cancellationToken); + } + public void Dispose() { SendStream?.Dispose(); diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs index 8f03e65e..11879f20 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MQTTnet.Packets; @@ -11,7 +10,7 @@ namespace MQTTnet.Adapter { IMqttPacketSerializer PacketSerializer { get; } - Task ConnectAsync(TimeSpan timeout); + Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken); Task DisconnectAsync(TimeSpan timeout); diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 087b1042..1d48f23b 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -32,12 +32,12 @@ namespace MQTTnet.Adapter public IMqttPacketSerializer PacketSerializer { get; } - public Task ConnectAsync(TimeSpan timeout) + public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken) { ThrowIfDisposed(); _logger.Verbose("Connecting [Timeout={0}]", timeout); - return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout)); + return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync(cancellationToken).TimeoutAfter(timeout)); } public Task DisconnectAsync(TimeSpan timeout) @@ -50,44 +50,32 @@ namespace MQTTnet.Adapter public async Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets) { + ThrowIfDisposed(); + foreach (var packet in packets) { - await SendPacketsAsync(timeout, cancellationToken, packet).ConfigureAwait(false); + if (packet == null) + { + continue; + } + + await SendPacketAsync(timeout, cancellationToken, packet).ConfigureAwait(false); } } - private Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket packet) + private Task SendPacketAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket packet) { - ThrowIfDisposed(); - - if (packet == null) + return ExecuteAndWrapExceptionAsync(() => { - return Task.FromResult(0); - } - - return ExecuteAndWrapExceptionAsync(async () => - { - if (cancellationToken.IsCancellationRequested) - { - return; - } - _logger.Verbose("TX >>> {0} [Timeout={1}]", packet, timeout); var packetData = PacketSerializer.Serialize(packet); - if (cancellationToken.IsCancellationRequested) - { - return; - } - - await _channel.SendStream.WriteAsync( + return _channel.WriteAsync( packetData.Array, packetData.Offset, packetData.Count, - cancellationToken).ConfigureAwait(false); - - await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false); + cancellationToken); }); } @@ -101,7 +89,6 @@ namespace MQTTnet.Adapter ReceivedMqttPacket receivedMqttPacket = null; try { - if (timeout > TimeSpan.Zero) { var timeoutCts = new CancellationTokenSource(timeout); @@ -109,14 +96,14 @@ namespace MQTTnet.Adapter try { - receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, linkedCts.Token).ConfigureAwait(false); + receivedMqttPacket = await ReceiveAsync(_channel, linkedCts.Token).ConfigureAwait(false); } - catch (OperationCanceledException ex) + catch (OperationCanceledException exception) { var timedOut = linkedCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested; if (timedOut) { - throw new MqttCommunicationTimedOutException(ex); + throw new MqttCommunicationTimedOutException(exception); } else { @@ -126,7 +113,7 @@ namespace MQTTnet.Adapter } else { - receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false); + receivedMqttPacket = await ReceiveAsync(_channel, cancellationToken).ConfigureAwait(false); } if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested) @@ -151,9 +138,9 @@ namespace MQTTnet.Adapter return packet; } - private static async Task ReceiveAsync(Stream stream, CancellationToken cancellationToken) + private static async Task ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken) { - var header = await MqttPacketReader.ReadHeaderAsync(stream, cancellationToken).ConfigureAwait(false); + var header = await MqttPacketReader.ReadHeaderAsync(channel, cancellationToken).ConfigureAwait(false); if (header == null) { return null; @@ -166,7 +153,7 @@ namespace MQTTnet.Adapter var body = header.BodyLength <= ReadBufferSize ? new MemoryStream(header.BodyLength) : new MemoryStream(); - var buffer = new byte[ReadBufferSize]; + var buffer = new byte[Math.Min(ReadBufferSize, header.BodyLength)]; while (body.Length < header.BodyLength) { var bytesLeft = header.BodyLength - (int)body.Length; @@ -175,7 +162,7 @@ namespace MQTTnet.Adapter bytesLeft = buffer.Length; } - var readBytesCount = await stream.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false); + var readBytesCount = await channel.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false); // Check if the client closed the connection before sending the full body. if (readBytesCount == 0) diff --git a/Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs b/Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs index b332884d..dd9d1f27 100644 --- a/Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs @@ -1,15 +1,15 @@ using System; -using System.IO; +using System.Threading; using System.Threading.Tasks; namespace MQTTnet.Channel { public interface IMqttChannel : IDisposable { - Stream SendStream { get; } - Stream ReceiveStream { get; } - - Task ConnectAsync(); + Task ConnectAsync(CancellationToken cancellationToken); Task DisconnectAsync(); + + Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); + Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); } } diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index 90790d5a..ee2189eb 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -60,7 +60,7 @@ namespace MQTTnet.Client _adapter = _adapterFactory.CreateClientAdapter(options, _logger); _logger.Verbose("Trying to connect with server."); - await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); + await _adapter.ConnectAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false); _logger.Verbose("Connection with server established."); await StartReceivingPacketsAsync().ConfigureAwait(false); @@ -92,14 +92,9 @@ namespace MQTTnet.Client public async Task DisconnectAsync() { - if (!IsConnected) - { - return; - } - try { - if (!_cancellationTokenSource.IsCancellationRequested) + if (IsConnected && !_cancellationTokenSource.IsCancellationRequested) { await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false); } diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs index ac96ce63..44bc5b44 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs @@ -6,7 +6,7 @@ public int? Port { get; set; } - public int BufferSize { get; set; } = 20 * 4096; + public int BufferSize { get; set; } = 4096; public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs index a9cd18e7..1487f0a7 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading; using System.Threading.Tasks; using Windows.Networking; using Windows.Networking.Sockets; @@ -17,36 +18,35 @@ namespace MQTTnet.Implementations { // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user. + public static int BufferSize { get; set; } = 4096; // Can be changed for fine tuning by library user. private readonly int _bufferSize = BufferSize; private readonly MqttClientTcpOptions _options; private StreamSocket _socket; + private Stream _readStream; + private Stream _writeStream; public MqttTcpChannel(MqttClientTcpOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); - _bufferSize = _options.BufferSize; + + _bufferSize = options.BufferSize; } public MqttTcpChannel(StreamSocket socket) { _socket = socket ?? throw new ArgumentNullException(nameof(socket)); - - CreateStreams(); } - public Stream SendStream { get; private set; } - public Stream ReceiveStream { get; private set; } - public static Func> CustomIgnorableServerCertificateErrorsResolver { get; set; } - public async Task ConnectAsync() + public async Task ConnectAsync(CancellationToken cancellationToken) { if (_socket == null) { _socket = new StreamSocket(); + _socket.Control.NoDelay = true; } if (!_options.TlsOptions.UseTls) @@ -65,7 +65,8 @@ namespace MQTTnet.Implementations await _socket.ConnectAsync(new HostName(_options.Server), _options.GetPort().ToString(), SocketProtectionLevel.Tls12); } - CreateStreams(); + _readStream = _socket.InputStream.AsStreamForRead(_bufferSize); + _writeStream = _socket.OutputStream.AsStreamForWrite(_bufferSize); } public Task DisconnectAsync() @@ -74,11 +75,22 @@ namespace MQTTnet.Implementations return Task.FromResult(0); } + public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _readStream.ReadAsync(buffer, offset, count, cancellationToken); + } + + public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + await _writeStream.WriteAsync(buffer, offset, count, cancellationToken); + await _writeStream.FlushAsync(cancellationToken); + } + public void Dispose() { try { - SendStream?.Dispose(); + _readStream?.Dispose(); } catch (ObjectDisposedException) { @@ -88,12 +100,12 @@ namespace MQTTnet.Implementations } finally { - SendStream = null; + _readStream = null; } try { - ReceiveStream?.Dispose(); + _writeStream?.Dispose(); } catch (ObjectDisposedException) { @@ -103,7 +115,7 @@ namespace MQTTnet.Implementations } finally { - ReceiveStream = null; + _writeStream = null; } try @@ -122,12 +134,6 @@ namespace MQTTnet.Implementations } } - private void CreateStreams() - { - SendStream = _socket.OutputStream.AsStreamForWrite(_bufferSize); - ReceiveStream = _socket.InputStream.AsStreamForRead(_bufferSize); - } - private static Certificate LoadCertificate(MqttClientTcpOptions options) { if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any()) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs index 0f9bba48..fe017810 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs @@ -7,6 +7,7 @@ using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using System.Linq; +using System.Threading; using MQTTnet.Channel; using MQTTnet.Client; @@ -14,20 +15,10 @@ namespace MQTTnet.Implementations { public sealed class MqttTcpChannel : IMqttChannel { -#if NET452 || NET461 || NETSTANDARD2_0 - // ReSharper disable once MemberCanBePrivate.Global - // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user. - - private readonly int _bufferSize = BufferSize; -#else - private readonly int _bufferSize = 0; -#endif - private readonly MqttClientTcpOptions _options; private Socket _socket; - private SslStream _sslStream; + private Stream _stream; /// /// called on client sockets are created in connect @@ -35,7 +26,6 @@ namespace MQTTnet.Implementations public MqttTcpChannel(MqttClientTcpOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); - _bufferSize = options.BufferSize; } /// @@ -45,21 +35,17 @@ namespace MQTTnet.Implementations public MqttTcpChannel(Socket socket, SslStream sslStream) { _socket = socket ?? throw new ArgumentNullException(nameof(socket)); - _sslStream = sslStream; - CreateStreams(); + CreateStream(sslStream); } - public Stream SendStream { get; private set; } - public Stream ReceiveStream { get; private set; } - public static Func CustomCertificateValidationCallback { get; set; } - public async Task ConnectAsync() + public async Task ConnectAsync(CancellationToken cancellationToken) { if (_socket == null) { - _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + _socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; } #if NET452 || NET461 @@ -68,15 +54,14 @@ namespace MQTTnet.Implementations await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false); #endif - _socket.NoDelay = true; - + SslStream sslStream = null; if (_options.TlsOptions.UseTls) { - _sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); - await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); + sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); + await sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); } - - CreateStreams(); + + CreateStream(sslStream); } public Task DisconnectAsync() @@ -85,13 +70,21 @@ namespace MQTTnet.Implementations return Task.FromResult(0); } + public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _stream.ReadAsync(buffer, offset, count, cancellationToken); + } + + public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _stream.WriteAsync(buffer, offset, count, cancellationToken); + } + public void Dispose() { - var oneStreamIsUsed = SendStream != null && ReceiveStream != null && ReferenceEquals(SendStream, ReceiveStream); - try { - SendStream?.Dispose(); + _stream?.Dispose(); } catch (ObjectDisposedException) { @@ -101,40 +94,7 @@ namespace MQTTnet.Implementations } finally { - SendStream = null; - } - - try - { - if (!oneStreamIsUsed) - { - ReceiveStream?.Dispose(); - } - } - catch (ObjectDisposedException) - { - } - catch (NullReferenceException) - { - } - finally - { - ReceiveStream = null; - } - - try - { - _sslStream?.Dispose(); - } - catch (ObjectDisposedException) - { - } - catch (NullReferenceException) - { - } - finally - { - _sslStream = null; + _stream = null; } try @@ -200,25 +160,16 @@ namespace MQTTnet.Implementations return certificates; } - private void CreateStreams() + private void CreateStream(Stream stream) { - Stream stream; - if (_sslStream != null) + if (stream != null) { - stream = _sslStream; + _stream = stream; } else { - stream = new NetworkStream(_socket, true); + _stream = new NetworkStream(_socket, true); } - -#if NET452 || NET461 || NETSTANDARD2_0 - SendStream = new BufferedStream(stream, _bufferSize); - ReceiveStream = new BufferedStream(stream, _bufferSize); -#else - SendStream = stream; - ReceiveStream = stream; -#endif } } } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs index e71d6fff..0d84ab46 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs @@ -62,6 +62,8 @@ namespace MQTTnet.Implementations { try { + args.Socket.Control.NoDelay = true; + var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs index b718dbbd..c9f38ab4 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs @@ -43,7 +43,7 @@ namespace MQTTnet.Implementations _defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort())); _defaultEndpointSocket.Listen(options.ConnectionBacklog); - Task.Run(async () => await AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false); + Task.Run(() => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); } if (options.TlsEndpointOptions.IsEnabled) @@ -63,7 +63,7 @@ namespace MQTTnet.Implementations _tlsEndpointSocket.Bind(new IPEndPoint(options.TlsEndpointOptions.BoundIPAddress, options.GetTlsEndpointPort())); _tlsEndpointSocket.Listen(options.ConnectionBacklog); - Task.Run(async () => await AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false); + Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); } return Task.FromResult(0); diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs index 057a07e5..4a9fe658 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs @@ -1,11 +1,13 @@ using System; -using System.IO; +using System.Collections.Generic; +using System.Linq; using System.Net.WebSockets; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using MQTTnet.Channel; using MQTTnet.Client; +using MQTTnet.Exceptions; namespace MQTTnet.Implementations { @@ -13,20 +15,25 @@ namespace MQTTnet.Implementations { // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user. + public static int BufferSize { get; set; } = 4096; // Can be changed for fine tuning by library user. + private readonly byte[] _chunckBuffer = new byte[BufferSize]; + private readonly Queue _buffer = new Queue(BufferSize); private readonly MqttClientWebSocketOptions _options; - private ClientWebSocket _webSocket; + + private WebSocket _webSocket; public MqttWebSocketChannel(MqttClientWebSocketOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); } - public Stream SendStream { get; private set; } - public Stream ReceiveStream { get; private set; } + public MqttWebSocketChannel(WebSocket webSocket) + { + _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); + } - public async Task ConnectAsync() + public async Task ConnectAsync(CancellationToken cancellationToken) { var uri = _options.Uri; if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase)) @@ -41,13 +48,13 @@ namespace MQTTnet.Implementations } } - _webSocket = new ClientWebSocket(); + var clientWebSocket = new ClientWebSocket(); if (_options.RequestHeaders != null) { foreach (var requestHeader in _options.RequestHeaders) { - _webSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); + clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); } } @@ -55,28 +62,26 @@ namespace MQTTnet.Implementations { foreach (var subProtocol in _options.SubProtocols) { - _webSocket.Options.AddSubProtocol(subProtocol); + clientWebSocket.Options.AddSubProtocol(subProtocol); } } if (_options.CookieContainer != null) { - _webSocket.Options.Cookies = _options.CookieContainer; + clientWebSocket.Options.Cookies = _options.CookieContainer; } if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null) { - _webSocket.Options.ClientCertificates = new X509CertificateCollection(); + clientWebSocket.Options.ClientCertificates = new X509CertificateCollection(); foreach (var certificate in _options.TlsOptions.Certificates) { - _webSocket.Options.ClientCertificates.Add(new X509Certificate(certificate)); + clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate)); } } - await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false); - - SendStream = new WebSocketStream(_webSocket); - ReceiveStream = SendStream; + await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false); + _webSocket = clientWebSocket; } public async Task DisconnectAsync() @@ -94,6 +99,56 @@ namespace MQTTnet.Implementations Dispose(); } + public async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var bytesRead = 0; + + // Use existing date from buffer. + while (count > 0 && _buffer.Any()) + { + buffer[offset] = _buffer.Dequeue(); + count--; + bytesRead++; + offset++; + } + + if (count == 0) + { + return bytesRead; + } + + // Fetch new data if the buffer is not full. + while (_webSocket.State == WebSocketState.Open) + { + await FetchChunkAsync(cancellationToken).ConfigureAwait(false); + + while (count > 0 && _buffer.Any()) + { + buffer[offset] = _buffer.Dequeue(); + count--; + bytesRead++; + offset++; + } + + if (count == 0) + { + return bytesRead; + } + } + + if (_webSocket.State == WebSocketState.Closed) + { + throw new MqttCommunicationException("WebSocket connection closed."); + } + + return bytesRead; + } + + public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _webSocket.SendAsync(new ArraySegment(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken); + } + public void Dispose() { try @@ -108,5 +163,26 @@ namespace MQTTnet.Implementations _webSocket = null; } } + + private async Task FetchChunkAsync(CancellationToken cancellationToken) + { + var response = await _webSocket.ReceiveAsync(new ArraySegment(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false); + + if (response.MessageType == WebSocketMessageType.Close) + { + await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); + } + else if (response.MessageType == WebSocketMessageType.Binary) + { + for (var i = 0; i < response.Count; i++) + { + _buffer.Enqueue(_chunckBuffer[i]); + } + } + else if (response.MessageType == WebSocketMessageType.Text) + { + throw new MqttProtocolViolationException("WebSocket channel received TEXT message."); + } + } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs deleted file mode 100644 index bad03935..00000000 --- a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Exceptions; - -namespace MQTTnet.Implementations -{ - public class WebSocketStream : Stream - { - private readonly byte[] _chunckBuffer = new byte[MqttWebSocketChannel.BufferSize]; - private readonly Queue _buffer = new Queue(MqttWebSocketChannel.BufferSize); - private readonly WebSocket _webSocket; - - public WebSocketStream(WebSocket webSocket) - { - _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); - } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override long Length => throw new NotSupportedException(); - - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - public override void Flush() - { - } - - public override Task FlushAsync(CancellationToken cancellationToken) - { - return Task.FromResult(0); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return ReadAsync(buffer, offset, count).GetAwaiter().GetResult(); - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - var bytesRead = 0; - - // Use existing date from buffer. - while (count > 0 && _buffer.Any()) - { - buffer[offset] = _buffer.Dequeue(); - count--; - bytesRead++; - offset++; - } - - if (count == 0) - { - return bytesRead; - } - - // Fetch new data if the buffer is not full. - while (_webSocket.State == WebSocketState.Open) - { - await FetchChunkAsync(cancellationToken).ConfigureAwait(false); - - while (count > 0 && _buffer.Any()) - { - buffer[offset] = _buffer.Dequeue(); - count--; - bytesRead++; - offset++; - } - - if (count == 0) - { - return bytesRead; - } - } - - if (_webSocket.State == WebSocketState.Closed) - { - throw new MqttCommunicationException("WebSocket connection closed."); - } - - return bytesRead; - } - - public override void Write(byte[] buffer, int offset, int count) - { - WriteAsync(buffer, offset, count).GetAwaiter().GetResult(); - } - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return _webSocket.SendAsync(new ArraySegment(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - private async Task FetchChunkAsync(CancellationToken cancellationToken) - { - var response = await _webSocket.ReceiveAsync(new ArraySegment(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false); - - if (response.MessageType == WebSocketMessageType.Close) - { - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); - } - else if (response.MessageType == WebSocketMessageType.Binary) - { - for (var i = 0; i < response.Count; i++) - { - _buffer.Enqueue(_chunckBuffer[i]); - } - } - else if (response.MessageType == WebSocketMessageType.Text) - { - throw new MqttProtocolViolationException("WebSocket channel received TEXT message."); - } - } - } -} diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs index 6afdd984..36883303 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using MQTTnet.Packets; @@ -11,6 +10,6 @@ namespace MQTTnet.Serializer ArraySegment Serialize(MqttBasePacket mqttPacket); - MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body); + MqttBasePacket Deserialize(MqttPacketHeader header, Stream body); } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs index bd43f7d3..4c435790 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs @@ -1,9 +1,9 @@ using System; -using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; +using MQTTnet.Channel; using MQTTnet.Exceptions; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -13,7 +13,7 @@ namespace MQTTnet.Serializer public sealed class MqttPacketReader : BinaryReader { private readonly MqttPacketHeader _header; - + public MqttPacketReader(MqttPacketHeader header, Stream bodyStream) : base(bodyStream, Encoding.UTF8, true) { @@ -22,7 +22,7 @@ namespace MQTTnet.Serializer public bool EndOfRemainingData => BaseStream.Position == _header.BodyLength; - public static async Task ReadHeaderAsync(Stream stream, CancellationToken cancellationToken) + public static async Task ReadHeaderAsync(IMqttChannel stream, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -33,7 +33,7 @@ namespace MQTTnet.Serializer // some large delay and thus the thread should be put back to the pool (await). So ReadByte() // is not an option here. var buffer = new byte[1]; - var readCount = await stream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); + var readCount = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); if (readCount <= 0) { return null; @@ -89,15 +89,14 @@ namespace MQTTnet.Serializer return ReadBytes(_header.BodyLength - (int)BaseStream.Position); } - private static async Task ReadBodyLengthAsync(Stream stream, CancellationToken cancellationToken) + private static async Task ReadBodyLengthAsync(IMqttChannel stream, 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 = 1; var value = 0; - byte encodedByte; - + int encodedByte; var buffer = new byte[1]; - var readBytes = new List(); + do { if (cancellationToken.IsCancellationRequested) @@ -112,12 +111,11 @@ namespace MQTTnet.Serializer } encodedByte = buffer[0]; - readBytes.Add(encodedByte); value += (byte)(encodedByte & 127) * multiplier; if (multiplier > 128 * 128 * 128) { - throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)})."); + throw new MqttProtocolViolationException("Remaining length is invalid."); } multiplier *= 128; diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 2447e05b..8225e609 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -27,7 +27,7 @@ namespace MQTTnet.Serializer var fixedHeader = SerializePacket(packet, writer); - var remainingLength = MqttPacketWriter.GetRemainingLength((int)stream.Length - 5); + var remainingLength = MqttPacketWriter.EncodeRemainingLength((int)stream.Length - 5); var headerSize = remainingLength.Length + 1; var headerOffset = 5 - headerSize; @@ -47,7 +47,7 @@ namespace MQTTnet.Serializer } } - public MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body) + public MqttBasePacket Deserialize(MqttPacketHeader header, Stream body) { if (header == null) throw new ArgumentNullException(nameof(header)); if (body == null) throw new ArgumentNullException(nameof(body)); @@ -183,7 +183,7 @@ namespace MQTTnet.Serializer var topic = reader.ReadStringWithLengthPrefix(); - ushort packetIdentifier = 0; + ushort? packetIdentifier = null; if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { packetIdentifier = reader.ReadUInt16(); @@ -191,12 +191,12 @@ namespace MQTTnet.Serializer var packet = new MqttPublishPacket { + PacketIdentifier = packetIdentifier, Retain = retain, - QualityOfServiceLevel = qualityOfServiceLevel, - Dup = dup, Topic = topic, Payload = reader.ReadRemainingData(), - PacketIdentifier = packetIdentifier + QualityOfServiceLevel = qualityOfServiceLevel, + Dup = dup }; return packet; diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs index 253294b1..3de4961c 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs @@ -56,7 +56,7 @@ namespace MQTTnet.Serializer Write(value); } - public static byte[] GetRemainingLength(int length) + public static byte[] EncodeRemainingLength(int length) { if (length <= 0) { @@ -82,7 +82,8 @@ namespace MQTTnet.Serializer offset++; } while (x > 0); - return bytes.Take(offset).ToArray(); + Array.Resize(ref bytes, offset); + return bytes; } } } diff --git a/README.md b/README.md index 83a03a35..313fc720 100644 --- a/README.md +++ b/README.md @@ -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 ~40.000 messages / second)* +* Performance optimized (processing ~50.000 messages / second)* * Interfaces included for mocking and testing * Access to internal trace messages * Unit tested (~80 tests) diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs index 4d28035e..ca48ad9b 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs @@ -11,8 +11,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttPacketReader_EmptyStream() { - var memStream = new MemoryStream(); - var header = MqttPacketReader.ReadHeaderAsync(memStream, CancellationToken.None).GetAwaiter().GetResult(); + var header = MqttPacketReader.ReadHeaderAsync(new TestMqttChannel(new MemoryStream()), CancellationToken.None).GetAwaiter().GetResult(); Assert.IsNull(header); } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index a7421c6d..276475af 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -416,7 +416,7 @@ namespace MQTTnet.Core.Tests using (var headerStream = new MemoryStream(Join(buffer1))) { - var header = MqttPacketReader.ReadHeaderAsync(headerStream, CancellationToken.None).GetAwaiter().GetResult(); + var header = MqttPacketReader.ReadHeaderAsync(new TestMqttChannel(headerStream), CancellationToken.None).GetAwaiter().GetResult(); using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.BodyLength)) { diff --git a/Tests/MQTTnet.Core.Tests/TestMqttChannel.cs b/Tests/MQTTnet.Core.Tests/TestMqttChannel.cs new file mode 100644 index 00000000..2b4914b7 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/TestMqttChannel.cs @@ -0,0 +1,41 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Channel; + +namespace MQTTnet.Core.Tests +{ + public class TestMqttChannel : IMqttChannel + { + private readonly MemoryStream _stream; + + public TestMqttChannel(MemoryStream stream) + { + _stream = stream; + } + + public void Dispose() + { + } + + public Task ConnectAsync(CancellationToken cancellationToken) + { + return Task.FromResult(0); + } + + public Task DisconnectAsync() + { + return Task.FromResult(0); + } + + public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _stream.ReadAsync(buffer, offset, count, cancellationToken); + } + + public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _stream.WriteAsync(buffer, offset, count, cancellationToken); + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index f564e51a..6c1bec55 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; @@ -21,7 +20,7 @@ namespace MQTTnet.Core.Tests { } - public Task ConnectAsync(TimeSpan timeout) + public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken) { return Task.FromResult(0); } diff --git a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index b485533a..878a7884 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -59,7 +59,7 @@ namespace MQTTnet.TestApp.NetCore var sentMessagesCount = 0; while (stopwatch.ElapsedMilliseconds < 1000) { - await client.PublishAsync(messages).ConfigureAwait(false); + client.PublishAsync(messages).GetAwaiter().GetResult(); sentMessagesCount++; } From e4cf2978e6e9bfbce6f706da6d9554aa9f7547cd Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Tue, 17 Apr 2018 21:43:10 -0300 Subject: [PATCH 015/138] Remove useless sempahore object --- Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs | 2 -- MQTTnet.sln | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 1d48f23b..800b1030 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -19,7 +19,6 @@ namespace MQTTnet.Adapter private const int ReadBufferSize = 4096; // TODO: Move buffer size to config private bool _isDisposed; - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly IMqttNetLogger _logger; private readonly IMqttChannel _channel; @@ -232,7 +231,6 @@ namespace MQTTnet.Adapter public void Dispose() { _isDisposed = true; - _semaphore?.Dispose(); _channel?.Dispose(); } diff --git a/MQTTnet.sln b/MQTTnet.sln index fab76088..07830c30 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -39,6 +39,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}" EndProject Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM From 109e4b2cf13d0698e534da84e59539731e8b4c04 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 18 Apr 2018 21:14:31 +0200 Subject: [PATCH 016/138] Refactor the task timeout code to avoid still running tasks if the timeout is reached. --- .../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 33 +++++++-- .../Adapter/MqttChannelAdapter.cs | 36 +++------ .../Client/MqttPacketDispatcher.cs | 13 ++-- .../Internal/TaskExtensions.cs | 74 +++++++------------ Tests/MQTTnet.Core.Tests/ExtensionTests.cs | 32 ++++---- 5 files changed, 88 insertions(+), 100 deletions(-) diff --git a/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index 78836ea4..b8cfd47e 100644 --- a/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Concurrent; using System.Text; +using System.Threading; using System.Threading.Tasks; using MQTTnet.Client; -using MQTTnet.Internal; using MQTTnet.Protocol; namespace MQTTnet.Extensions.Rpc @@ -12,7 +12,7 @@ namespace MQTTnet.Extensions.Rpc { private readonly ConcurrentDictionary> _waitingCalls = new ConcurrentDictionary>(); private readonly IMqttClient _mqttClient; - + public MqttRpcClient(IMqttClient mqttClient) { _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); @@ -22,10 +22,20 @@ namespace MQTTnet.Extensions.Rpc public Task ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) { - return ExecuteAsync(timeout, methodName, Encoding.UTF8.GetBytes(payload), qualityOfServiceLevel); + return ExecuteAsync(timeout, methodName, Encoding.UTF8.GetBytes(payload), qualityOfServiceLevel, CancellationToken.None); } - public async Task ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel) + public Task ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken) + { + return ExecuteAsync(timeout, methodName, Encoding.UTF8.GetBytes(payload), qualityOfServiceLevel, cancellationToken); + } + + public Task ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel) + { + return ExecuteAsync(timeout, methodName, payload, qualityOfServiceLevel, CancellationToken.None); + } + + public async Task ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken) { if (methodName == null) throw new ArgumentNullException(nameof(methodName)); @@ -54,7 +64,20 @@ namespace MQTTnet.Extensions.Rpc await _mqttClient.SubscribeAsync(responseTopic, qualityOfServiceLevel).ConfigureAwait(false); await _mqttClient.PublishAsync(requestMessage).ConfigureAwait(false); - return await tcs.Task.TimeoutAfter(timeout); + using (var timeoutCts = new CancellationTokenSource(timeout)) + { + timeoutCts.Token.Register(() => + { + if (!tcs.Task.IsCompleted && !tcs.Task.IsFaulted && !tcs.Task.IsCanceled) + { + tcs.TrySetCanceled(); + } + }); + + var result = await tcs.Task.ConfigureAwait(false); + timeoutCts.Cancel(false); + return result; + } } finally { diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 1d48f23b..79192c46 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using MQTTnet.Channel; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; -using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Serializer; @@ -18,11 +17,11 @@ namespace MQTTnet.Adapter private const uint ErrorOperationAborted = 0x800703E3; private const int ReadBufferSize = 4096; // TODO: Move buffer size to config - private bool _isDisposed; - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly IMqttNetLogger _logger; private readonly IMqttChannel _channel; + private bool _isDisposed; + public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetLogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -37,7 +36,8 @@ namespace MQTTnet.Adapter ThrowIfDisposed(); _logger.Verbose("Connecting [Timeout={0}]", timeout); - return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync(cancellationToken).TimeoutAfter(timeout)); + return ExecuteAndWrapExceptionAsync(() => + Internal.TaskExtensions.TimeoutAfter(ct => _channel.ConnectAsync(ct), timeout, cancellationToken)); } public Task DisconnectAsync(TimeSpan timeout) @@ -45,7 +45,8 @@ namespace MQTTnet.Adapter ThrowIfDisposed(); _logger.Verbose("Disconnecting [Timeout={0}]", timeout); - return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout)); + return ExecuteAndWrapExceptionAsync(() => + Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, CancellationToken.None)); } public async Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets) @@ -71,11 +72,11 @@ namespace MQTTnet.Adapter var packetData = PacketSerializer.Serialize(packet); - return _channel.WriteAsync( + return Internal.TaskExtensions.TimeoutAfter(ct => _channel.WriteAsync( packetData.Array, packetData.Offset, packetData.Count, - cancellationToken); + ct), timeout, cancellationToken); }); } @@ -91,25 +92,7 @@ namespace MQTTnet.Adapter { if (timeout > TimeSpan.Zero) { - var timeoutCts = new CancellationTokenSource(timeout); - var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); - - try - { - receivedMqttPacket = await ReceiveAsync(_channel, linkedCts.Token).ConfigureAwait(false); - } - catch (OperationCanceledException exception) - { - var timedOut = linkedCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested; - if (timedOut) - { - throw new MqttCommunicationTimedOutException(exception); - } - else - { - throw; - } - } + receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfter(ct => ReceiveAsync(_channel, ct), timeout, cancellationToken).ConfigureAwait(false); } else { @@ -232,7 +215,6 @@ namespace MQTTnet.Adapter public void Dispose() { _isDisposed = true; - _semaphore?.Dispose(); _channel?.Dispose(); } diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs index 172e53bd..5035a1e4 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Concurrent; +using System.Threading; using System.Threading.Tasks; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; -using MQTTnet.Internal; using MQTTnet.Packets; namespace MQTTnet.Client { public class MqttPacketDispatcher { - private readonly ConcurrentDictionary, TaskCompletionSource> _awaiters = new ConcurrentDictionary, TaskCompletionSource>(); private readonly IMqttNetLogger _logger; @@ -23,8 +22,8 @@ namespace MQTTnet.Client { var packetAwaiter = AddPacketAwaiter(responseType, identifier); try - { - return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false); + { + return await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, timeout, CancellationToken.None).ConfigureAwait(false); } catch (MqttCommunicationTimedOutException) { @@ -49,7 +48,7 @@ namespace MQTTnet.Client var type = packet.GetType(); var key = new Tuple(identifier, type); - + if (_awaiters.TryRemove(key, out var tcs)) { @@ -74,8 +73,8 @@ namespace MQTTnet.Client identifier = 0; } - var dictionaryKey = new Tuple(identifier, responseType); - if (!_awaiters.TryAdd(dictionaryKey,tcs)) + var dictionaryKey = new Tuple(identifier, responseType); + if (!_awaiters.TryAdd(dictionaryKey, tcs)) { throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}."); } diff --git a/Frameworks/MQTTnet.NetStandard/Internal/TaskExtensions.cs b/Frameworks/MQTTnet.NetStandard/Internal/TaskExtensions.cs index 876c5495..288ac0ba 100644 --- a/Frameworks/MQTTnet.NetStandard/Internal/TaskExtensions.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/TaskExtensions.cs @@ -7,72 +7,52 @@ namespace MQTTnet.Internal { public static class TaskExtensions { - public static async Task TimeoutAfter(this Task task, TimeSpan timeout) + public static async Task TimeoutAfter(Func action, TimeSpan timeout, CancellationToken cancellationToken) { - if (task == null) throw new ArgumentNullException(nameof(task)); + if (action == null) throw new ArgumentNullException(nameof(action)); - using (var timeoutCts = new CancellationTokenSource()) + using (var timeoutCts = new CancellationTokenSource(timeout)) + using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken)) { try { - var timeoutTask = Task.Delay(timeout, timeoutCts.Token); - var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false); - - if (finishedTask == timeoutTask) - { - throw new MqttCommunicationTimedOutException(); - } - - if (task.IsCanceled) - { - throw new TaskCanceledException(); - } - - if (task.IsFaulted) - { - throw new MqttCommunicationException(task.Exception?.GetBaseException()); - } + await action(linkedCts.Token).ConfigureAwait(false); } - finally + catch (OperationCanceledException exception) { - timeoutCts.Cancel(); + var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested; + if (timeoutReached) + { + throw new MqttCommunicationTimedOutException(exception); + } + + throw; } } } - public static async Task TimeoutAfter(this Task task, TimeSpan timeout) + public static async Task TimeoutAfter(Func> action, TimeSpan timeout, CancellationToken cancellationToken) { - if (task == null) throw new ArgumentNullException(nameof(task)); + if (action == null) throw new ArgumentNullException(nameof(action)); - using (var timeoutCts = new CancellationTokenSource()) + using (var timeoutCts = new CancellationTokenSource(timeout)) + using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken)) { try { - var timeoutTask = Task.Delay(timeout, timeoutCts.Token); - var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false); - - if (finishedTask == timeoutTask) - { - throw new MqttCommunicationTimedOutException(); - } - - if (task.IsCanceled) - { - throw new TaskCanceledException(); - } - - if (task.IsFaulted) - { - throw new MqttCommunicationException(task.Exception.GetBaseException()); - } - - return task.Result; + return await action(linkedCts.Token).ConfigureAwait(false); } - finally + catch (OperationCanceledException exception) { - timeoutCts.Cancel(); + var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested; + if (timeoutReached) + { + throw new MqttCommunicationTimedOutException(exception); + } + + throw; } } - } + } } } diff --git a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs index 053b0ca3..daeea620 100644 --- a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs +++ b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs @@ -1,9 +1,9 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Exceptions; -using MQTTnet.Internal; namespace MQTTnet.Core.Tests { @@ -14,20 +14,20 @@ namespace MQTTnet.Core.Tests [TestMethod] public async Task TimeoutAfter() { - await Task.Delay(TimeSpan.FromMilliseconds(500)).TimeoutAfter(TimeSpan.FromMilliseconds(100)); + await Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct), TimeSpan.FromMilliseconds(100), CancellationToken.None); } [ExpectedException(typeof(MqttCommunicationTimedOutException))] [TestMethod] public async Task TimeoutAfterWithResult() { - await Task.Delay(TimeSpan.FromMilliseconds(500)).ContinueWith(t => 5).TimeoutAfter(TimeSpan.FromMilliseconds(100)); + await Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(100), CancellationToken.None); } [TestMethod] public async Task TimeoutAfterCompleteInTime() { - var result = await Task.Delay(TimeSpan.FromMilliseconds(100)).ContinueWith(t => 5).TimeoutAfter(TimeSpan.FromMilliseconds(500)); + var result = await Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(100), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(500), CancellationToken.None); Assert.AreEqual(5, result); } @@ -36,17 +36,17 @@ namespace MQTTnet.Core.Tests { try { - await Task.Run(() => + await Internal.TaskExtensions.TimeoutAfter(ct => Task.Run(() => { var iis = new int[0]; iis[1] = 0; - }).TimeoutAfter(TimeSpan.FromSeconds(1)); + }, ct), TimeSpan.FromSeconds(1), CancellationToken.None); Assert.Fail(); } - catch (MqttCommunicationException e) + catch (Exception e) { - Assert.IsTrue(e.InnerException is IndexOutOfRangeException); + Assert.IsTrue(e is IndexOutOfRangeException); } } @@ -55,17 +55,18 @@ namespace MQTTnet.Core.Tests { try { - await Task.Run(() => + await Internal.TaskExtensions.TimeoutAfter(ct => Task.Run(() => { var iis = new int[0]; - return iis[1]; - }).TimeoutAfter(TimeSpan.FromSeconds(1)); + iis[1] = 0; + return iis[0]; + }, ct), TimeSpan.FromSeconds(1), CancellationToken.None); Assert.Fail(); } - catch (MqttCommunicationException e) + catch (Exception e) { - Assert.IsTrue(e.InnerException is IndexOutOfRangeException); + Assert.IsTrue(e is IndexOutOfRangeException); } } @@ -73,7 +74,10 @@ namespace MQTTnet.Core.Tests public async Task TimeoutAfterMemoryUsage() { var tasks = Enumerable.Range(0, 100000) - .Select(i => Task.Delay(TimeSpan.FromMilliseconds(1)).TimeoutAfter(TimeSpan.FromMinutes(1))); + .Select(i => + { + return Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct), TimeSpan.FromMinutes(1), CancellationToken.None); + }); await Task.WhenAll(tasks); AssertIsLess(3_000_000, GC.GetTotalMemory(true)); From cb09bac246209a8045c6a50808f0f923de4a9e99 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 18 Apr 2018 23:10:00 +0200 Subject: [PATCH 017/138] Refactor the WebSocket channel and remove the internal queue. --- .../Client/MqttClientWebSocketOptions.cs | 2 + .../Implementations/MqttWebSocketChannel.cs | 84 ++++++++----------- 2 files changed, 39 insertions(+), 47 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs index 114283d4..9718298a 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs @@ -13,6 +13,8 @@ namespace MQTTnet.Client public CookieContainer CookieContainer { get; set; } + public int BufferSize { get; set; } = 4096; + public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); } } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs index 4a9fe658..9688ced1 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Net.WebSockets; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -13,19 +11,19 @@ namespace MQTTnet.Implementations { public sealed class MqttWebSocketChannel : IMqttChannel { - // ReSharper disable once MemberCanBePrivate.Global - // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - public static int BufferSize { get; set; } = 4096; // Can be changed for fine tuning by library user. - - private readonly byte[] _chunckBuffer = new byte[BufferSize]; - private readonly Queue _buffer = new Queue(BufferSize); private readonly MqttClientWebSocketOptions _options; + private readonly byte[] _chunkBuffer; + + private int _chunkBufferLength; + private int _chunkBufferOffset; private WebSocket _webSocket; public MqttWebSocketChannel(MqttClientWebSocketOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); + + _chunkBuffer = new byte[options.BufferSize]; } public MqttWebSocketChannel(WebSocket webSocket) @@ -49,13 +47,13 @@ namespace MQTTnet.Implementations } var clientWebSocket = new ClientWebSocket(); - + if (_options.RequestHeaders != null) { foreach (var requestHeader in _options.RequestHeaders) { clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); - } + } } if (_options.SubProtocols != null) @@ -103,28 +101,18 @@ namespace MQTTnet.Implementations { var bytesRead = 0; - // Use existing date from buffer. - while (count > 0 && _buffer.Any()) - { - buffer[offset] = _buffer.Dequeue(); - count--; - bytesRead++; - offset++; - } - - if (count == 0) - { - return bytesRead; - } - - // Fetch new data if the buffer is not full. while (_webSocket.State == WebSocketState.Open) { - await FetchChunkAsync(cancellationToken).ConfigureAwait(false); - - while (count > 0 && _buffer.Any()) + await EnsureFilledChunkBufferAsync(cancellationToken).ConfigureAwait(false); + if (_chunkBufferLength == 0) { - buffer[offset] = _buffer.Dequeue(); + return 0; + } + + while (count > 0 && _chunkBufferOffset < _chunkBufferLength) + { + buffer[offset] = _chunkBuffer[_chunkBufferOffset]; + _chunkBufferOffset++; count--; bytesRead++; offset++; @@ -135,12 +123,7 @@ namespace MQTTnet.Implementations return bytesRead; } } - - if (_webSocket.State == WebSocketState.Closed) - { - throw new MqttCommunicationException("WebSocket connection closed."); - } - + return bytesRead; } @@ -161,25 +144,32 @@ namespace MQTTnet.Implementations finally { _webSocket = null; - } + } } - private async Task FetchChunkAsync(CancellationToken cancellationToken) + private async Task EnsureFilledChunkBufferAsync(CancellationToken cancellationToken) { - var response = await _webSocket.ReceiveAsync(new ArraySegment(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false); + if (_chunkBufferOffset < _chunkBufferLength) + { + return; + } + + if (_webSocket.State == WebSocketState.Closed) + { + throw new MqttCommunicationException("WebSocket connection closed."); + } + + var response = await _webSocket.ReceiveAsync(new ArraySegment(_chunkBuffer, 0, _chunkBuffer.Length), cancellationToken).ConfigureAwait(false); + _chunkBufferLength = response.Count; + _chunkBufferOffset = 0; if (response.MessageType == WebSocketMessageType.Close) { - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); + throw new MqttCommunicationException("The WebSocket server closed the connection."); + //await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); } - else if (response.MessageType == WebSocketMessageType.Binary) - { - for (var i = 0; i < response.Count; i++) - { - _buffer.Enqueue(_chunckBuffer[i]); - } - } - else if (response.MessageType == WebSocketMessageType.Text) + + if (response.MessageType == WebSocketMessageType.Text) { throw new MqttProtocolViolationException("WebSocket channel received TEXT message."); } From 942bf705821a392bdb121c021c190f4422fbea0e Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Thu, 19 Apr 2018 19:38:03 -0300 Subject: [PATCH 018/138] one less copy and allocation --- .../Serializer/MqttPacketSerializer.cs | 7 +++---- .../Serializer/MqttPacketWriter.cs | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 8225e609..6848330a 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -24,19 +24,18 @@ namespace MQTTnet.Serializer { // Leave enough head space for max header size (fixed + 4 variable remaining length) stream.Position = 5; - var fixedHeader = SerializePacket(packet, writer); - var remainingLength = MqttPacketWriter.EncodeRemainingLength((int)stream.Length - 5); + stream.Position = 1; + var remainingLength = MqttPacketWriter.EncodeRemainingLength((int)stream.Length - 5, stream); - var headerSize = remainingLength.Length + 1; + var headerSize = remainingLength + 1; var headerOffset = 5 - headerSize; // Position cursor on correct offset on beginining of array (has leading 0x0) stream.Position = headerOffset; writer.Write(fixedHeader); - writer.Write(remainingLength, 0, remainingLength.Length); #if NET461 || NET452 || NETSTANDARD2_0 var buffer = stream.GetBuffer(); diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs index 3de4961c..368d224e 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs @@ -56,14 +56,18 @@ namespace MQTTnet.Serializer Write(value); } - public static byte[] EncodeRemainingLength(int length) + public static int EncodeRemainingLength(int length,MemoryStream stream) { + //write the encoded remaining length right aligned on the 4 byte buffer + if (length <= 0) { - return new[] { (byte)0 }; + stream.Seek(3, SeekOrigin.Current); + stream.WriteByte(0); + return 1; } - var bytes = new byte[4]; + byte[] buffer = new byte[4]; var offset = 0; // Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html. @@ -77,13 +81,15 @@ namespace MQTTnet.Serializer encodedByte = encodedByte | 128; } - bytes[offset] = (byte)encodedByte; + buffer[offset] = (byte)encodedByte; offset++; } while (x > 0); - Array.Resize(ref bytes, offset); - return bytes; + stream.Seek(4 - offset, SeekOrigin.Current); + stream.Write(buffer, 0, offset); + + return offset; } } } From 04a40807687b20a45e767be55c04e6d3f469370c Mon Sep 17 00:00:00 2001 From: Israel Lot Date: Thu, 19 Apr 2018 19:45:01 -0300 Subject: [PATCH 019/138] removed performance section from sln file --- MQTTnet.sln | 3 --- 1 file changed, 3 deletions(-) diff --git a/MQTTnet.sln b/MQTTnet.sln index 07830c30..fab76088 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -39,9 +39,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}" EndProject Global - GlobalSection(Performance) = preSolution - HasPerformanceSessions = true - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM From af8d1ec6be95e5c923764c396a60bd8d20adf56e Mon Sep 17 00:00:00 2001 From: Christian Date: Sat, 21 Apr 2018 12:55:09 +0200 Subject: [PATCH 020/138] Refactoring --- Build/build.ps1 | 10 +-- .../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 26 +++++-- .../MqttWebSocketServerAdapter.cs | 6 +- .../MqttWebSocketServerChannel.cs | 72 ------------------- .../Adapter/IMqttServerAdapter.cs | 2 +- .../Adapter/MqttChannelAdapter.cs | 2 +- .../Client/MqttPacketDispatcher.cs | 17 +++-- .../MqttTcpServerAdapter.Uwp.cs | 2 +- .../Implementations/MqttTcpServerAdapter.cs | 2 +- .../Implementations/MqttWebSocketChannel.cs | 63 +--------------- .../ManagedClient/ManagedMqttClient.cs | 7 +- .../TestMqttServerAdapter.cs | 4 ++ .../MainPage.xaml.cs | 6 +- 13 files changed, 54 insertions(+), 165 deletions(-) delete mode 100644 Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs diff --git a/Build/build.ps1 b/Build/build.ps1 index 021ca1d6..021858f7 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -20,11 +20,11 @@ if ($path) { &$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Build the RPC extension - &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m - &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m - &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m - &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m - &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m + &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue diff --git a/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index b8cfd47e..e8242791 100644 --- a/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MQTTnet.Client; +using MQTTnet.Exceptions; using MQTTnet.Protocol; namespace MQTTnet.Extensions.Rpc @@ -63,10 +64,11 @@ namespace MQTTnet.Extensions.Rpc await _mqttClient.SubscribeAsync(responseTopic, qualityOfServiceLevel).ConfigureAwait(false); await _mqttClient.PublishAsync(requestMessage).ConfigureAwait(false); - + using (var timeoutCts = new CancellationTokenSource(timeout)) + using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token)) { - timeoutCts.Token.Register(() => + linkedCts.Token.Register(() => { if (!tcs.Task.IsCompleted && !tcs.Task.IsFaulted && !tcs.Task.IsCanceled) { @@ -74,9 +76,23 @@ namespace MQTTnet.Extensions.Rpc } }); - var result = await tcs.Task.ConfigureAwait(false); - timeoutCts.Cancel(false); - return result; + try + { + var result = await tcs.Task.ConfigureAwait(false); + timeoutCts.Cancel(false); + return result; + } + catch (TaskCanceledException taskCanceledException) + { + if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + { + throw new MqttCommunicationTimedOutException(taskCanceledException); + } + else + { + throw; + } + } } } finally diff --git a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index c84b9be3..babc59b2 100644 --- a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -3,12 +3,13 @@ using System.Net.WebSockets; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; +using MQTTnet.Implementations; using MQTTnet.Serializer; using MQTTnet.Server; namespace MQTTnet.AspNetCore { - public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable + public class MqttWebSocketServerAdapter : IMqttServerAdapter { public event EventHandler ClientAccepted; @@ -26,8 +27,7 @@ namespace MQTTnet.AspNetCore { if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); - var channel = new MqttWebSocketServerChannel(webSocket); - var clientAdapter = new MqttChannelAdapter(channel, new MqttPacketSerializer(), new MqttNetLogger()); + var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket), new MqttPacketSerializer(), new MqttNetLogger()); var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); ClientAccepted?.Invoke(this, eventArgs); diff --git a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs deleted file mode 100644 index 82bd915c..00000000 --- a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.IO; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Channel; -using MQTTnet.Implementations; - -namespace MQTTnet.AspNetCore -{ - public class MqttWebSocketServerChannel : IMqttChannel - { - private WebSocket _webSocket; - private readonly MqttWebSocketChannel _channel; - - public MqttWebSocketServerChannel(WebSocket webSocket) - { - _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); - - - _channel = new MqttWebSocketChannel(webSocket); - ReceiveStream = SendStream; - } - - private Stream SendStream { get; set; } - private Stream ReceiveStream { get; set; } - - public Task ConnectAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - public async Task DisconnectAsync() - { - if (_webSocket == null) - { - return; - } - - try - { - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false); - } - finally - { - Dispose(); - } - } - - public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return ReceiveStream.ReadAsync(buffer, offset, count, cancellationToken); - } - - public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return SendStream.WriteAsync(buffer, offset, count, cancellationToken); - } - - public void Dispose() - { - SendStream?.Dispose(); - ReceiveStream?.Dispose(); - - _webSocket?.Dispose(); - - SendStream = null; - ReceiveStream = null; - _webSocket = null; - } - } -} \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttServerAdapter.cs index bd583f54..eff9eab7 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttServerAdapter.cs @@ -4,7 +4,7 @@ using MQTTnet.Server; namespace MQTTnet.Adapter { - public interface IMqttServerAdapter + public interface IMqttServerAdapter : IDisposable { event EventHandler ClientAccepted; diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 79192c46..8fce65ea 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -134,7 +134,7 @@ namespace MQTTnet.Adapter return new ReceivedMqttPacket(header, new MemoryStream(new byte[0], false)); } - var body = header.BodyLength <= ReadBufferSize ? new MemoryStream(header.BodyLength) : new MemoryStream(); + var body = new MemoryStream(header.BodyLength); var buffer = new byte[Math.Min(ReadBufferSize, header.BodyLength)]; while (body.Length < header.BodyLength) diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs index 5035a1e4..4569d124 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs @@ -10,7 +10,7 @@ namespace MQTTnet.Client { public class MqttPacketDispatcher { - private readonly ConcurrentDictionary, TaskCompletionSource> _awaiters = new ConcurrentDictionary, TaskCompletionSource>(); + private readonly ConcurrentDictionary, TaskCompletionSource> _awaiters = new ConcurrentDictionary, TaskCompletionSource>(); private readonly IMqttNetLogger _logger; public MqttPacketDispatcher(IMqttNetLogger logger) @@ -40,16 +40,15 @@ namespace MQTTnet.Client { if (packet == null) throw new ArgumentNullException(nameof(packet)); - ushort? identifier = 0; - if (packet is IMqttPacketWithIdentifier packetWithIdentifier) + ushort identifier = 0; + if (packet is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue) { - identifier = packetWithIdentifier.PacketIdentifier; + identifier = packetWithIdentifier.PacketIdentifier.Value; } var type = packet.GetType(); - var key = new Tuple(identifier, type); - - + var key = new Tuple(identifier, type); + if (_awaiters.TryRemove(key, out var tcs)) { tcs.TrySetResult(packet); @@ -73,7 +72,7 @@ namespace MQTTnet.Client identifier = 0; } - var dictionaryKey = new Tuple(identifier, responseType); + var dictionaryKey = new Tuple(identifier ?? 0, responseType); if (!_awaiters.TryAdd(dictionaryKey, tcs)) { throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}."); @@ -89,7 +88,7 @@ namespace MQTTnet.Client identifier = 0; } - var dictionaryKey = new Tuple(identifier, responseType); + var dictionaryKey = new Tuple(identifier ?? 0, responseType); _awaiters.TryRemove(dictionaryKey, out var _); } } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs index 0d84ab46..c6b0ff45 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs @@ -9,7 +9,7 @@ using MQTTnet.Server; namespace MQTTnet.Implementations { - public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable + public class MqttTcpServerAdapter : IMqttServerAdapter { private readonly IMqttNetLogger _logger; private StreamSocketListener _defaultEndpointSocket; diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs index c9f38ab4..56457b52 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs @@ -14,7 +14,7 @@ using MQTTnet.Server; namespace MQTTnet.Implementations { - public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable + public class MqttTcpServerAdapter : IMqttServerAdapter { private readonly IMqttNetLogger _logger; diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs index 9688ced1..74d26858 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs @@ -5,25 +5,18 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Channel; using MQTTnet.Client; -using MQTTnet.Exceptions; namespace MQTTnet.Implementations { public sealed class MqttWebSocketChannel : IMqttChannel { private readonly MqttClientWebSocketOptions _options; - private readonly byte[] _chunkBuffer; - - private int _chunkBufferLength; - private int _chunkBufferOffset; private WebSocket _webSocket; public MqttWebSocketChannel(MqttClientWebSocketOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); - - _chunkBuffer = new byte[options.BufferSize]; } public MqttWebSocketChannel(WebSocket webSocket) @@ -99,32 +92,8 @@ namespace MQTTnet.Implementations public async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - var bytesRead = 0; - - while (_webSocket.State == WebSocketState.Open) - { - await EnsureFilledChunkBufferAsync(cancellationToken).ConfigureAwait(false); - if (_chunkBufferLength == 0) - { - return 0; - } - - while (count > 0 && _chunkBufferOffset < _chunkBufferLength) - { - buffer[offset] = _chunkBuffer[_chunkBufferOffset]; - _chunkBufferOffset++; - count--; - bytesRead++; - offset++; - } - - if (count == 0) - { - return bytesRead; - } - } - - return bytesRead; + var response = await _webSocket.ReceiveAsync(new ArraySegment(buffer, offset, count), cancellationToken).ConfigureAwait(false); + return response.Count; } public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) @@ -146,33 +115,5 @@ namespace MQTTnet.Implementations _webSocket = null; } } - - private async Task EnsureFilledChunkBufferAsync(CancellationToken cancellationToken) - { - if (_chunkBufferOffset < _chunkBufferLength) - { - return; - } - - if (_webSocket.State == WebSocketState.Closed) - { - throw new MqttCommunicationException("WebSocket connection closed."); - } - - var response = await _webSocket.ReceiveAsync(new ArraySegment(_chunkBuffer, 0, _chunkBuffer.Length), cancellationToken).ConfigureAwait(false); - _chunkBufferLength = response.Count; - _chunkBufferOffset = 0; - - if (response.MessageType == WebSocketMessageType.Close) - { - throw new MqttCommunicationException("The WebSocket server closed the connection."); - //await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); - } - - if (response.MessageType == WebSocketMessageType.Text) - { - throw new MqttProtocolViolationException("WebSocket channel received TEXT message."); - } - } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs index f1ce45d9..bbfc1c78 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs @@ -71,7 +71,7 @@ namespace MQTTnet.ManagedClient _connectionCancellationToken = new CancellationTokenSource(); #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(async () => await MaintainConnectionAsync(_connectionCancellationToken.Token).ConfigureAwait(false), _connectionCancellationToken.Token).ConfigureAwait(false); + Task.Run(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed _logger.Info("Started"); @@ -190,10 +190,7 @@ namespace MQTTnet.ManagedClient if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed) { await SynchronizeSubscriptionsAsync().ConfigureAwait(false); - StartPublishing(); - - return; } @@ -375,7 +372,7 @@ namespace MQTTnet.ManagedClient _publishingCancellationToken = cts; #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(async () => await PublishQueuedMessagesAsync(cts.Token).ConfigureAwait(false), cts.Token).ConfigureAwait(false); + Task.Run(() => PublishQueuedMessagesAsync(cts.Token), cts.Token); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed } diff --git a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs index a2fec80f..8b9d0ba0 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs @@ -71,5 +71,9 @@ namespace MQTTnet.Core.Tests { return Task.FromResult(0); } + + public void Dispose() + { + } } } \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index d907f1d8..3de538ad 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -354,13 +354,17 @@ namespace MQTTnet.TestApp.UniversalWindows { var rpcClient = new MqttRpcClient(_mqttClient); var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), RpcMethod.Text, payload, qos); - + RpcResponses.Items.Add(RpcMethod.Text + " >>> " + Encoding.UTF8.GetString(response)); } catch (MqttCommunicationTimedOutException) { RpcResponses.Items.Add(RpcMethod.Text + " >>> [TIMEOUT]"); } + catch (Exception exception) + { + RpcResponses.Items.Add(RpcMethod.Text + " >>> [EXCEPTION (" + exception.Message + ")]"); + } } private void ClearRpcResponses(object sender, RoutedEventArgs e) From ffb1645f149f3afd69765e1e5c2e59a5afa389a6 Mon Sep 17 00:00:00 2001 From: Christian Date: Sat, 21 Apr 2018 13:07:11 +0200 Subject: [PATCH 021/138] Refactoring --- .../MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs index 368d224e..54f40eb2 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Text; using MQTTnet.Protocol; @@ -56,9 +55,9 @@ namespace MQTTnet.Serializer Write(value); } - public static int EncodeRemainingLength(int length,MemoryStream stream) + public static int EncodeRemainingLength(int length, MemoryStream stream) { - //write the encoded remaining length right aligned on the 4 byte buffer + // write the encoded remaining length right aligned on the 4 byte buffer if (length <= 0) { @@ -67,7 +66,7 @@ namespace MQTTnet.Serializer return 1; } - byte[] buffer = new byte[4]; + var buffer = new byte[4]; var offset = 0; // Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html. From 9e90111b37cfbfc93c391beeb687b2d90df06ef5 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 24 Apr 2018 20:54:18 +0200 Subject: [PATCH 022/138] Fix several deadlocks --- Build/MQTTnet.nuspec | 3 + .../Adapter/IMqttChannelAdapter.cs | 5 +- .../Adapter/MqttChannelAdapter.cs | 8 +- .../MQTTnet.NetStandard/Client/MqttClient.cs | 83 ++++---- .../Client/MqttClientOptionsBuilder.cs | 6 + .../Client/MqttPacketDispatcher.cs | 43 ++-- .../Implementations/MqttWebSocketChannel.cs | 18 +- .../Serializer/MqttPacketSerializer.cs | 40 ++-- .../Server/ConnectedMqttClient.cs | 6 +- .../Server/MqttClientPendingMessagesQueue.cs | 2 +- .../Server/MqttClientSession.cs | 108 +++++----- .../Server/MqttClientSessionsManager.cs | 190 ++++++++---------- .../Server/MqttClientSubscriptionsManager.cs | 13 +- .../MQTTnet.NetStandard/Server/MqttServer.cs | 25 +-- README.md | 2 +- .../MqttPacketSerializerTests.cs | 14 +- .../MqttSubscriptionsManagerTests.cs | 12 +- .../TestMqttCommunicationAdapter.cs | 5 +- Tests/MQTTnet.TestApp.NetCore/Program.cs | 5 + .../PublicBrokerTest.cs | 128 ++++++++++++ 20 files changed, 428 insertions(+), 288 deletions(-) create mode 100644 Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 8a26bd40..4fb8719b 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -12,6 +12,9 @@ MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). * [Client] Added new overloads for the message builder. * [Core] Performance optimizations (thanks to @ israellot) +* [Core] Fixed a memory leak which was caused by not properly stopped async tasks. +* [Client] Fixed a deadlock when connecting to a not reachable server. +* [Server] Fixed a deadlock when reusing the same _ClientId_ while a will message is used (thanks to @william-wps). Copyright Christian Kratky 2016-2018 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 diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs index 11879f20..0970affc 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MQTTnet.Packets; @@ -12,9 +13,9 @@ namespace MQTTnet.Adapter Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken); - Task DisconnectAsync(TimeSpan timeout); + Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken); - Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets); + Task SendPacketsAsync(TimeSpan timeout, IEnumerable packets, CancellationToken cancellationToken); Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken); } diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 8fce65ea..894b8d97 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Net.Sockets; using System.Runtime.InteropServices; @@ -40,16 +41,16 @@ namespace MQTTnet.Adapter Internal.TaskExtensions.TimeoutAfter(ct => _channel.ConnectAsync(ct), timeout, cancellationToken)); } - public Task DisconnectAsync(TimeSpan timeout) + public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken) { ThrowIfDisposed(); _logger.Verbose("Disconnecting [Timeout={0}]", timeout); return ExecuteAndWrapExceptionAsync(() => - Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, CancellationToken.None)); + Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, cancellationToken)); } - public async Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets) + public async Task SendPacketsAsync(TimeSpan timeout, IEnumerable packets, CancellationToken cancellationToken) { ThrowIfDisposed(); @@ -215,6 +216,7 @@ namespace MQTTnet.Adapter public void Dispose() { _isDisposed = true; + _channel?.Dispose(); } diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index ee2189eb..dcbe8ae4 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -18,12 +18,12 @@ 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 MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); + private readonly IMqttClientAdapterFactory _adapterFactory; - private readonly MqttPacketDispatcher _packetDispatcher; private readonly IMqttNetLogger _logger; private IMqttClientOptions _options; - private bool _isReceivingPackets; private CancellationTokenSource _cancellationTokenSource; private Task _packetReceiverTask; private Task _keepAliveMessageSenderTask; @@ -33,8 +33,6 @@ namespace MQTTnet.Client { _adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - - _packetDispatcher = new MqttPacketDispatcher(logger); } public event EventHandler Connected; @@ -63,7 +61,7 @@ namespace MQTTnet.Client await _adapter.ConnectAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false); _logger.Verbose("Connection with server established."); - await StartReceivingPacketsAsync().ConfigureAwait(false); + StartReceivingPackets(_cancellationTokenSource.Token); var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false); _logger.Verbose("MQTT connection with server established."); @@ -72,7 +70,7 @@ namespace MQTTnet.Client if (_options.KeepAlivePeriod != TimeSpan.Zero) { - StartSendingKeepAliveMessages(); + StartSendingKeepAliveMessages(_cancellationTokenSource.Token); } IsConnected = true; @@ -271,9 +269,9 @@ namespace MQTTnet.Client if (_adapter != null) { - await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); + await _adapter.DisconnectAsync(_options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false); } - + _logger.Verbose("Disconnected from adapter."); } catch (Exception adapterException) @@ -383,30 +381,45 @@ namespace MQTTnet.Client private Task SendAsync(params MqttBasePacket[] packets) { _sendTracker.Restart(); - return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packets); + return _adapter.SendPacketsAsync(_options.CommunicationTimeout, packets, _cancellationTokenSource.Token); } private async Task SendAndReceiveAsync(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket { - ushort? identifier = null; - if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier) + _sendTracker.Restart(); + + ushort identifier = 0; + if (requestPacket is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue) { - identifier = requestPacketWithIdentifier.PacketIdentifier; + identifier = packetWithIdentifier.PacketIdentifier.Value; } - var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout); - await SendAsync(requestPacket).ConfigureAwait(false); + var packetAwaiter = _packetDispatcher.AddPacketAwaiter(identifier); + try + { + await _adapter.SendPacketsAsync(_options.CommunicationTimeout, new[] { requestPacket }, _cancellationTokenSource.Token).ConfigureAwait(false); + var respone = await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, _options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false); - return (TResponsePacket)await packetAwaiter.ConfigureAwait(false); + return (TResponsePacket)respone; + } + catch (MqttCommunicationTimedOutException) + { + _logger.Warning($"Timeout while waiting for packet of type '{typeof(TResponsePacket).Namespace}'."); + throw; + } + finally + { + _packetDispatcher.RemovePacketAwaiter(identifier); + } } - private async Task SendKeepAliveMessagesAsync() + private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken) { _logger.Verbose("Start sending keep alive packets."); try { - while (!_cancellationTokenSource.Token.IsCancellationRequested) + while (!cancellationToken.IsCancellationRequested) { var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75); if (_options.KeepAliveSendInterval.HasValue) @@ -419,7 +432,7 @@ namespace MQTTnet.Client await SendAndReceiveAsync(new MqttPingReqPacket()).ConfigureAwait(false); } - await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).ConfigureAwait(false); + await Task.Delay(keepAliveSendInterval, cancellationToken).ConfigureAwait(false); } } catch (Exception exception) @@ -435,7 +448,7 @@ namespace MQTTnet.Client { _logger.Error(exception, "Unhandled exception while sending/receiving keep alive packets."); } - + await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false); } finally @@ -444,24 +457,22 @@ namespace MQTTnet.Client } } - private async Task ReceivePacketsAsync() + private async Task ReceivePacketsAsync(CancellationToken cancellationToken) { _logger.Verbose("Start receiving packets."); try { - while (!_cancellationTokenSource.Token.IsCancellationRequested) + while (!cancellationToken.IsCancellationRequested) { - _isReceivingPackets = true; + var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); - var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false); - - if (_cancellationTokenSource.Token.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) { return; } - StartProcessReceivedPacket(packet); + StartProcessReceivedPacket(packet, cancellationToken); } } catch (Exception exception) @@ -479,6 +490,7 @@ namespace MQTTnet.Client } await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false); + _packetDispatcher.Dispatch(exception); } finally { @@ -486,26 +498,19 @@ namespace MQTTnet.Client } } - private void StartProcessReceivedPacket(MqttBasePacket packet) + private void StartProcessReceivedPacket(MqttBasePacket packet, CancellationToken cancellationToken) { - Task.Run(() => ProcessReceivedPacketAsync(packet), _cancellationTokenSource.Token); + Task.Run(() => ProcessReceivedPacketAsync(packet), cancellationToken); } - private async Task StartReceivingPacketsAsync() + private void StartReceivingPackets(CancellationToken cancellationToken) { - _isReceivingPackets = false; - - _packetReceiverTask = Task.Run(ReceivePacketsAsync, _cancellationTokenSource.Token); - - while (!_isReceivingPackets && !_cancellationTokenSource.Token.IsCancellationRequested) - { - await Task.Delay(TimeSpan.FromMilliseconds(100), _cancellationTokenSource.Token).ConfigureAwait(false); - } + _packetReceiverTask = Task.Run(() => ReceivePacketsAsync(cancellationToken), cancellationToken); } - private void StartSendingKeepAliveMessages() + private void StartSendingKeepAliveMessages(CancellationToken cancellationToken) { - _keepAliveMessageSenderTask = Task.Run(SendKeepAliveMessagesAsync, _cancellationTokenSource.Token); + _keepAliveMessageSenderTask = Task.Run(() => SendKeepAliveMessagesAsync(cancellationToken), cancellationToken); } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs index ad766c3f..985cda7c 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs @@ -36,6 +36,12 @@ namespace MQTTnet.Client return this; } + public MqttClientOptionsBuilder WithKeepAliveSendInterval(TimeSpan value) + { + _options.KeepAliveSendInterval = value; + return this; + } + public MqttClientOptionsBuilder WithClientId(string value) { _options.ClientId = value; diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs index 4569d124..1a1021c4 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Concurrent; -using System.Threading; using System.Threading.Tasks; -using MQTTnet.Diagnostics; -using MQTTnet.Exceptions; using MQTTnet.Packets; namespace MQTTnet.Client @@ -11,28 +8,12 @@ namespace MQTTnet.Client public class MqttPacketDispatcher { private readonly ConcurrentDictionary, TaskCompletionSource> _awaiters = new ConcurrentDictionary, TaskCompletionSource>(); - private readonly IMqttNetLogger _logger; - - public MqttPacketDispatcher(IMqttNetLogger logger) + + public void Dispatch(Exception exception) { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task WaitForPacketAsync(Type responseType, ushort? identifier, TimeSpan timeout) - { - var packetAwaiter = AddPacketAwaiter(responseType, identifier); - try + foreach (var awaiter in _awaiters) { - return await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, timeout, CancellationToken.None).ConfigureAwait(false); - } - catch (MqttCommunicationTimedOutException) - { - _logger.Warning("Timeout while waiting for packet of type '{0}'.", responseType.Name); - throw; - } - finally - { - RemovePacketAwaiter(responseType, identifier); + awaiter.Value.SetException(exception); } } @@ -63,7 +44,7 @@ namespace MQTTnet.Client _awaiters.Clear(); } - private TaskCompletionSource AddPacketAwaiter(Type responseType, ushort? identifier) + public TaskCompletionSource AddPacketAwaiter(ushort? identifier) where TResponsePacket : MqttBasePacket { var tcs = new TaskCompletionSource(); @@ -71,25 +52,25 @@ namespace MQTTnet.Client { identifier = 0; } - - var dictionaryKey = new Tuple(identifier ?? 0, responseType); - if (!_awaiters.TryAdd(dictionaryKey, tcs)) + + var key = new Tuple(identifier ?? 0, typeof(TResponsePacket)); + if (!_awaiters.TryAdd(key, tcs)) { - throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}."); + throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{key.Item2.Name}' with identifier {key.Item1}."); } return tcs; } - private void RemovePacketAwaiter(Type responseType, ushort? identifier) + public void RemovePacketAwaiter(ushort? identifier) where TResponsePacket : MqttBasePacket { if (!identifier.HasValue) { identifier = 0; } - var dictionaryKey = new Tuple(identifier ?? 0, responseType); - _awaiters.TryRemove(dictionaryKey, out var _); + var key = new Tuple(identifier ?? 0, typeof(TResponsePacket)); + _awaiters.TryRemove(key, out var _); } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs index 74d26858..5ceecbba 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs @@ -10,6 +10,7 @@ namespace MQTTnet.Implementations { public sealed class MqttWebSocketChannel : IMqttChannel { + private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); private readonly MqttClientWebSocketOptions _options; private WebSocket _webSocket; @@ -96,13 +97,26 @@ namespace MQTTnet.Implementations return response.Count; } - public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - return _webSocket.SendAsync(new ArraySegment(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken); + // This lock is required because the client will throw an exception if _SendAsync_ is + // called from multiple threads at the same time. But this issue only happens with several + // framework versions. + await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + await _webSocket.SendAsync(new ArraySegment(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false); + } + finally + { + _sendLock.Release(); + } } public void Dispose() { + _sendLock?.Dispose(); + try { _webSocket?.Dispose(); diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 6848330a..18818ce8 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -11,7 +11,7 @@ namespace MQTTnet.Serializer public sealed class MqttPacketSerializer : IMqttPacketSerializer { private static byte[] ProtocolVersionV311Name { get; } = Encoding.UTF8.GetBytes("MQTT"); - private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIs"); + private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIsdp"); public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; @@ -203,22 +203,30 @@ namespace MQTTnet.Serializer private static MqttBasePacket DeserializeConnect(MqttPacketReader reader) { - reader.ReadBytes(2); // Skip 2 bytes + reader.ReadBytes(2); // Skip 2 bytes for header and remaining length. MqttProtocolVersion protocolVersion; var protocolName = reader.ReadBytes(4); - if (protocolName.SequenceEqual(ProtocolVersionV310Name)) - { - reader.ReadBytes(2); - protocolVersion = MqttProtocolVersion.V310; - } - else if (protocolName.SequenceEqual(ProtocolVersionV311Name)) + + if (protocolName.SequenceEqual(ProtocolVersionV311Name)) { protocolVersion = MqttProtocolVersion.V311; } else { - throw new MqttProtocolViolationException("Protocol name is not supported."); + var buffer = new byte[6]; + Array.Copy(protocolName, buffer, 4); + protocolName = reader.ReadBytes(2); + Array.Copy(protocolName, 0, buffer, 4, 2); + + if (protocolName.SequenceEqual(ProtocolVersionV310Name)) + { + protocolVersion = MqttProtocolVersion.V310; + } + else + { + throw new MqttProtocolViolationException("Protocol name is not supported."); + } } reader.ReadByte(); // Skip protocol level @@ -323,16 +331,15 @@ namespace MQTTnet.Serializer ValidateConnectPacket(packet); // Write variable header - writer.Write(0x00, 0x04); // 3.1.2.1 Protocol Name if (ProtocolVersion == MqttProtocolVersion.V311) { - writer.Write(ProtocolVersionV311Name); - writer.Write(0x04); // 3.1.2.2 Protocol Level (4) + writer.WriteWithLengthPrefix(ProtocolVersionV311Name); + writer.Write(0x04); // 3.1.2.2 Protocol Level 4 } else { - writer.Write(ProtocolVersionV310Name); - writer.Write(0x64, 0x70, 0x03); // Protocol Level (0x03) + writer.WriteWithLengthPrefix(ProtocolVersionV310Name); + writer.Write(0x03); // Protocol Level 3 } var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags @@ -351,6 +358,11 @@ namespace MQTTnet.Serializer connectFlags.Write(false); } + if (packet.Password != null && packet.Username == null) + { + 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); diff --git a/Frameworks/MQTTnet.NetStandard/Server/ConnectedMqttClient.cs b/Frameworks/MQTTnet.NetStandard/Server/ConnectedMqttClient.cs index 4fa8e4ea..c12759a0 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/ConnectedMqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/ConnectedMqttClient.cs @@ -3,11 +3,15 @@ using MQTTnet.Serializer; namespace MQTTnet.Server { + // TODO: Rename to "RegisteredClient" + // TODO: Add IsConnected + // TODO: Add interface + public class ConnectedMqttClient { public string ClientId { get; set; } - public MqttProtocolVersion ProtocolVersion { get; set; } + public MqttProtocolVersion? ProtocolVersion { get; set; } public TimeSpan LastPacketReceived { get; set; } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs index 9e7ebd72..d19ff463 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs @@ -93,7 +93,7 @@ namespace MQTTnet.Server return; } - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false); + await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { packet }, cancellationToken).ConfigureAwait(false); _logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs index 8514da99..83a684a2 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs @@ -16,11 +16,11 @@ namespace MQTTnet.Server public sealed class MqttClientSession : IDisposable { private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); - private readonly IMqttServerOptions _options; - private readonly IMqttNetLogger _logger; private readonly MqttRetainedMessagesManager _retainedMessagesManager; + private readonly IMqttNetLogger _logger; + private readonly IMqttServerOptions _options; + private readonly MqttClientSessionsManager _sessionsManager; - private IMqttChannelAdapter _adapter; private CancellationTokenSource _cancellationTokenSource; private MqttApplicationMessage _willMessage; private bool _wasCleanDisconnect; @@ -28,22 +28,22 @@ namespace MQTTnet.Server public MqttClientSession( string clientId, IMqttServerOptions options, + MqttClientSessionsManager sessionsManager, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger) { _options = options ?? throw new ArgumentNullException(nameof(options)); + _sessionsManager = sessionsManager; _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); ClientId = clientId; KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeoutAsync, _logger); - SubscriptionsManager = new MqttClientSubscriptionsManager(_options, clientId); + SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server); PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); } - public Func ApplicationMessageReceivedCallback { get; set; } - public MqttClientSubscriptionsManager SubscriptionsManager { get; } public MqttClientPendingMessagesQueue PendingMessagesQueue { get; } @@ -52,9 +52,9 @@ namespace MQTTnet.Server public string ClientId { get; } - public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion; + public MqttProtocolVersion? ProtocolVersion { get; private set; } - public bool IsConnected => _adapter != null; + public bool IsConnected { get; private set; } public async Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) { @@ -63,66 +63,73 @@ namespace MQTTnet.Server try { - var cancellationTokenSource = new CancellationTokenSource(); + _cancellationTokenSource = new CancellationTokenSource(); _wasCleanDisconnect = false; _willMessage = connectPacket.WillMessage; - _adapter = adapter; - _cancellationTokenSource = cancellationTokenSource; - PendingMessagesQueue.Start(adapter, cancellationTokenSource.Token); - KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, cancellationTokenSource.Token); + IsConnected = true; + ProtocolVersion = adapter.PacketSerializer.ProtocolVersion; - await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false); + PendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token); + KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token); + + await ReceivePacketsAsync(adapter, _cancellationTokenSource.Token).ConfigureAwait(false); } catch (OperationCanceledException) { } catch (MqttCommunicationException exception) { - _logger.Warning(exception, "Client '{0}': Communication exception while processing client packets.", ClientId); + _logger.Warning(exception, + "Client '{0}': Communication exception while processing client packets.", ClientId); } catch (Exception exception) { - _logger.Error(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); + _logger.Error(exception, + "Client '{0}': Unhandled exception while processing client packets.", ClientId); + } + finally + { + ProtocolVersion = null; + IsConnected = false; + + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; } return _wasCleanDisconnect; } - public async Task StopAsync(bool wasCleanDisconnect = false) + public Task StopAsync(bool wasCleanDisconnect = false) { try { if (_cancellationTokenSource == null) { - return; + return Task.FromResult(0); } _wasCleanDisconnect = wasCleanDisconnect; _cancellationTokenSource?.Cancel(false); - PendingMessagesQueue.WaitForCompletion(); KeepAliveMonitor.WaitForCompletion(); - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; - - _adapter = null; - - _logger.Info("Client '{0}': Session stopped.", ClientId); - } - finally - { var willMessage = _willMessage; _willMessage = null; // clear willmessage so it is send just once - if (willMessage != null && !wasCleanDisconnect) + if (_willMessage != null && !wasCleanDisconnect) { - await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false); + _sessionsManager.StartDispatchApplicationMessage(this, willMessage); } } + finally + { + _logger.Info("Client '{0}': Session stopped.", ClientId); + } + + return Task.FromResult(0); } public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage) @@ -170,8 +177,6 @@ namespace MQTTnet.Server public void Dispose() { - ApplicationMessageReceivedCallback = null; - SubscriptionsManager?.Dispose(); PendingMessagesQueue?.Dispose(); @@ -219,7 +224,7 @@ namespace MQTTnet.Server if (packet is MqttPingReqPacket) { - return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttPingRespPacket() }); + return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { new MqttPingRespPacket() }, cancellationToken); } if (packet is MqttPubRelPacket pubRelPacket) @@ -234,7 +239,7 @@ namespace MQTTnet.Server PacketIdentifier = pubRecPacket.PacketIdentifier }; - return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { responsePacket }); + return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { responsePacket }, cancellationToken); } if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) @@ -267,10 +272,19 @@ namespace MQTTnet.Server return StopAsync(); } + private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters) + { + var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters); + foreach (var applicationMessage in retainedMessages) + { + await EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false); + } + } + private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) { var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false); - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { subscribeResult.ResponsePacket }).ConfigureAwait(false); + await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { subscribeResult.ResponsePacket }, cancellationToken).ConfigureAwait(false); if (subscribeResult.CloseConnection) { @@ -283,16 +297,7 @@ namespace MQTTnet.Server private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) { var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false); - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { unsubscribeResult }); - } - - private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters) - { - var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters); - foreach (var applicationMessage in retainedMessages) - { - await EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false); - } + await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { unsubscribeResult }, cancellationToken); } private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) @@ -303,7 +308,8 @@ namespace MQTTnet.Server { case MqttQualityOfServiceLevel.AtMostOnce: { - return ApplicationMessageReceivedCallback?.Invoke(this, applicationMessage); + _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage); + return Task.FromResult(0); } case MqttQualityOfServiceLevel.AtLeastOnce: { @@ -322,25 +328,25 @@ namespace MQTTnet.Server private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) { - await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false); + _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage); var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }; - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false); + await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false); } private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) { // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] - await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false); + _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage); var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false); + await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false); } private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) { var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }; - return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }); + return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken); } } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index 4698f7cf..45854cf0 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -8,38 +8,34 @@ using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Packets; using MQTTnet.Protocol; -using MQTTnet.Serializer; namespace MQTTnet.Server { public sealed class MqttClientSessionsManager : IDisposable { private readonly Dictionary _sessions = new Dictionary(); - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim _sessionsLock = new SemaphoreSlim(1, 1); - private readonly IMqttServerOptions _options; private readonly MqttRetainedMessagesManager _retainedMessagesManager; + private readonly IMqttServerOptions _options; private readonly IMqttNetLogger _logger; - public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger) + public MqttClientSessionsManager(IMqttServerOptions options, MqttServer server, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _options = options ?? throw new ArgumentNullException(nameof(options)); + Server = server ?? throw new ArgumentNullException(nameof(server)); _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); } - public Action ClientConnectedCallback { get; set; } - public Action ClientDisconnectedCallback { get; set; } - public Action ClientSubscribedTopicCallback { get; set; } - public Action ClientUnsubscribedTopicCallback { get; set; } - public Action ApplicationMessageReceivedCallback { get; set; } + public MqttServer Server { get; } public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) { var clientId = string.Empty; var wasCleanDisconnect = false; MqttClientSession clientSession = null; - + try { if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken) @@ -57,30 +53,30 @@ namespace MQTTnet.Server var connectReturnCode = ValidateConnection(connectPacket); if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { - await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] + await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { new MqttConnAckPacket { ConnectReturnCode = connectReturnCode } - }).ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); return; } - var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false); + var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false); clientSession = result.Session; - await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] + await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { new MqttConnAckPacket { ConnectReturnCode = connectReturnCode, IsSessionPresent = result.IsExistingSession } - }).ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); - ClientConnectedCallback?.Invoke(new ConnectedMqttClient + Server.OnClientConnected(new ConnectedMqttClient { ClientId = clientId, ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion @@ -99,15 +95,15 @@ namespace MQTTnet.Server { try { - await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); + await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false); clientAdapter.Dispose(); } catch (Exception exception) { _logger.Error(exception, exception.Message); } - - ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient + + Server.OnClientDisconnected(new ConnectedMqttClient { ClientId = clientId, ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion, @@ -119,7 +115,7 @@ namespace MQTTnet.Server public async Task StopAsync() { - await _semaphore.WaitAsync().ConfigureAwait(false); + await _sessionsLock.WaitAsync().ConfigureAwait(false); try { foreach (var session in _sessions) @@ -131,19 +127,19 @@ namespace MQTTnet.Server } finally { - _semaphore.Release(); + _sessionsLock.Release(); } } public async Task> GetConnectedClientsAsync() { - await _semaphore.WaitAsync().ConfigureAwait(false); + await _sessionsLock.WaitAsync().ConfigureAwait(false); try { return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient { ClientId = s.Value.ClientId, - ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311, + ProtocolVersion = s.Value.ProtocolVersion, LastPacketReceived = s.Value.KeepAliveMonitor.LastPacketReceived, LastNonKeepAlivePacketReceived = s.Value.KeepAliveMonitor.LastNonKeepAlivePacketReceived, PendingApplicationMessages = s.Value.PendingMessagesQueue.Count @@ -151,49 +147,13 @@ namespace MQTTnet.Server } finally { - _semaphore.Release(); + _sessionsLock.Release(); } } - public async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) + public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) { - try - { - var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage); - if (interceptorContext.CloseConnection) - { - await senderClientSession.StopAsync().ConfigureAwait(false); - } - - if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish) - { - return; - } - - if (applicationMessage.Retain) - { - await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false); - } - - ApplicationMessageReceivedCallback?.Invoke(senderClientSession?.ClientId, applicationMessage); - } - catch (Exception exception) - { - _logger.Error(exception, "Error while processing application message"); - } - - await _semaphore.WaitAsync().ConfigureAwait(false); - try - { - foreach (var clientSession in _sessions.Values) - { - await clientSession.EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false); - } - } - finally - { - _semaphore.Release(); - } + Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage)); } public async Task SubscribeAsync(string clientId, IList topicFilters) @@ -201,19 +161,19 @@ namespace MQTTnet.Server if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - await _semaphore.WaitAsync().ConfigureAwait(false); + await _sessionsLock.WaitAsync().ConfigureAwait(false); try { if (!_sessions.TryGetValue(clientId, out var session)) { - throw new InvalidOperationException($"Client session {clientId} is unknown."); + throw new InvalidOperationException($"Client session '{clientId}' is unknown."); } - await session.SubscribeAsync(topicFilters); + await session.SubscribeAsync(topicFilters).ConfigureAwait(false); } finally { - _semaphore.Release(); + _sessionsLock.Release(); } } @@ -222,36 +182,25 @@ namespace MQTTnet.Server if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - await _semaphore.WaitAsync().ConfigureAwait(false); + await _sessionsLock.WaitAsync().ConfigureAwait(false); try { if (!_sessions.TryGetValue(clientId, out var session)) { - throw new InvalidOperationException($"Client session {clientId} is unknown."); + throw new InvalidOperationException($"Client session '{clientId}' is unknown."); } - await session.UnsubscribeAsync(topicFilters); + await session.UnsubscribeAsync(topicFilters).ConfigureAwait(false); } finally { - _semaphore.Release(); + _sessionsLock.Release(); } } - private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) + public void Dispose() { - var interceptorContext = new MqttApplicationMessageInterceptorContext( - senderClientSession?.ClientId, - applicationMessage); - - var interceptor = _options.ApplicationMessageInterceptor; - if (interceptor == null) - { - return interceptorContext; - } - - interceptor(interceptorContext); - return interceptorContext; + _sessionsLock?.Dispose(); } private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) @@ -271,9 +220,9 @@ namespace MQTTnet.Server return context.ReturnCode; } - private async Task GetOrCreateClientSessionAsync(MqttConnectPacket connectPacket) + private async Task PrepareClientSessionAsync(MqttConnectPacket connectPacket) { - await _semaphore.WaitAsync().ConfigureAwait(false); + await _sessionsLock.WaitAsync().ConfigureAwait(false); try { var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); @@ -300,14 +249,7 @@ namespace MQTTnet.Server { isExistingSession = false; - clientSession = new MqttClientSession(connectPacket.ClientId, _options, _retainedMessagesManager, _logger) - { - ApplicationMessageReceivedCallback = DispatchApplicationMessageAsync - }; - - clientSession.SubscriptionsManager.TopicSubscribedCallback = ClientSubscribedTopicCallback; - clientSession.SubscriptionsManager.TopicUnsubscribedCallback = ClientUnsubscribedTopicCallback; - + clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _retainedMessagesManager, _logger); _sessions[connectPacket.ClientId] = clientSession; _logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId); @@ -317,19 +259,65 @@ namespace MQTTnet.Server } finally { - _semaphore.Release(); + _sessionsLock.Release(); } } - public void Dispose() + private async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) { - ClientConnectedCallback = null; - ClientDisconnectedCallback = null; - ClientSubscribedTopicCallback = null; - ClientUnsubscribedTopicCallback = null; - ApplicationMessageReceivedCallback = null; + try + { + var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage); + if (interceptorContext.CloseConnection) + { + await senderClientSession.StopAsync().ConfigureAwait(false); + } - _semaphore?.Dispose(); + if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish) + { + return; + } + + if (applicationMessage.Retain) + { + await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false); + } + + Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage); + } + catch (Exception exception) + { + _logger.Error(exception, "Error while processing application message"); + } + + await _sessionsLock.WaitAsync().ConfigureAwait(false); + try + { + foreach (var clientSession in _sessions.Values) + { + await clientSession.EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false); + } + } + finally + { + _sessionsLock.Release(); + } + } + + private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) + { + var interceptorContext = new MqttApplicationMessageInterceptorContext( + senderClientSession?.ClientId, + applicationMessage); + + var interceptor = _options.ApplicationMessageInterceptor; + if (interceptor == null) + { + return interceptorContext; + } + + interceptor(interceptorContext); + return interceptorContext; } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs index 93182174..a49723af 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs @@ -13,17 +13,16 @@ namespace MQTTnet.Server private readonly Dictionary _subscriptions = new Dictionary(); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly IMqttServerOptions _options; + private readonly MqttServer _server; private readonly string _clientId; - public MqttClientSubscriptionsManager(IMqttServerOptions options, string clientId) + public MqttClientSubscriptionsManager(string clientId, IMqttServerOptions options, MqttServer server) { - _options = options ?? throw new ArgumentNullException(nameof(options)); _clientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + _server = server; } - public Action TopicSubscribedCallback { get; set; } - public Action TopicUnsubscribedCallback { get; set; } - public async Task SubscribeAsync(MqttSubscribePacket subscribePacket) { if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); @@ -61,7 +60,7 @@ namespace MQTTnet.Server if (interceptorContext.AcceptSubscription) { _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; - TopicSubscribedCallback?.Invoke(_clientId, topicFilter); + _server.OnClientSubscribedTopic(_clientId, topicFilter); } } } @@ -83,7 +82,7 @@ namespace MQTTnet.Server foreach (var topicFilter in unsubscribePacket.TopicFilters) { _subscriptions.Remove(topicFilter); - TopicUnsubscribedCallback?.Invoke(_clientId, topicFilter); + _server.OnClientUnsubscribedTopic(_clientId, topicFilter); } } finally diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs index 63294420..b32869d9 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs @@ -57,7 +57,7 @@ namespace MQTTnet.Server return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters); } - public async Task PublishAsync(IEnumerable applicationMessages) + public Task PublishAsync(IEnumerable applicationMessages) { if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); @@ -65,8 +65,10 @@ namespace MQTTnet.Server foreach (var applicationMessage in applicationMessages) { - await _clientSessionsManager.DispatchApplicationMessageAsync(null, applicationMessage); + _clientSessionsManager.StartDispatchApplicationMessage(null, applicationMessage); } + + return Task.FromResult(0); } public async Task StartAsync(IMqttServerOptions options) @@ -80,14 +82,7 @@ namespace MQTTnet.Server _retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger); await _retainedMessagesManager.LoadMessagesAsync(); - _clientSessionsManager = new MqttClientSessionsManager(Options, _retainedMessagesManager, _logger) - { - ClientConnectedCallback = OnClientConnected, - ClientDisconnectedCallback = OnClientDisconnected, - ClientSubscribedTopicCallback = OnClientSubscribedTopic, - ClientUnsubscribedTopicCallback = OnClientUnsubscribedTopic, - ApplicationMessageReceivedCallback = OnApplicationMessageReceived - }; + _clientSessionsManager = new MqttClientSessionsManager(Options, this, _retainedMessagesManager, _logger); foreach (var adapter in _adapters) { @@ -132,29 +127,29 @@ namespace MQTTnet.Server } } - private void OnClientConnected(ConnectedMqttClient client) + internal void OnClientConnected(ConnectedMqttClient client) { _logger.Info("Client '{0}': Connected.", client.ClientId); ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client)); } - private void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect) + internal void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect) { _logger.Info("Client '{0}': Disconnected (clean={1}).", client.ClientId, wasCleanDisconnect); ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client, wasCleanDisconnect)); } - private void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter) + internal void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter) { ClientSubscribedTopic?.Invoke(this, new MqttClientSubscribedTopicEventArgs(clientId, topicFilter)); } - private void OnClientUnsubscribedTopic(string clientId, string topicFilter) + internal void OnClientUnsubscribedTopic(string clientId, string topicFilter) { ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter)); } - private void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage) + internal void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage) { ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage)); } diff --git a/README.md b/README.md index 313fc720..409cbae2 100644 --- a/README.md +++ b/README.md @@ -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 ~50.000 messages / second)* +* Performance optimized (processing ~60.000 messages / second)* * Interfaces included for mocking and testing * Access to internal trace messages * Unit tested (~80 tests) diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 276475af..580bc4d4 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; @@ -25,7 +24,7 @@ namespace MQTTnet.Core.Tests CleanSession = true }; - SerializeAndCompare(p, "EB0ABE1RSXNkcAPCAHsAA1hZWgAEVVNFUgAEUEFTUw==", MqttProtocolVersion.V310); + SerializeAndCompare(p, "EB0ABk1RSXNkcAPCAHsAA1hZWgAEVVNFUgAEUEFTUw==", MqttProtocolVersion.V310); } [TestMethod] @@ -428,17 +427,6 @@ namespace MQTTnet.Core.Tests } } - private static byte[] Join(IEnumerable> chunks) - { - var buffer = new MemoryStream(); - foreach (var chunk in chunks) - { - buffer.Write(chunk.Array, chunk.Offset, chunk.Count); - } - - return buffer.ToArray(); - } - private static byte[] Join(params ArraySegment[] chunks) { var buffer = new MemoryStream(); diff --git a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs index 8b2863af..13464155 100644 --- a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs @@ -1,4 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Adapter; +using MQTTnet.Diagnostics; using MQTTnet.Packets; using MQTTnet.Protocol; using MQTTnet.Server; @@ -11,7 +13,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeSingleSuccess() { - var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), ""); + var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger())); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); @@ -32,7 +34,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeDifferentQoSSuccess() { - var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), ""); + var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger())); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtMostOnce)); @@ -53,7 +55,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeTwoTimesSuccess() { - var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), ""); + var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger())); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)); @@ -75,7 +77,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeSingleNoSuccess() { - var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), ""); + var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger())); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); @@ -94,7 +96,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle() { - var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), ""); + var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger())); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index 6c1bec55..808e08a3 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; @@ -25,12 +26,12 @@ namespace MQTTnet.Core.Tests return Task.FromResult(0); } - public Task DisconnectAsync(TimeSpan timeout) + public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken) { return Task.FromResult(0); } - public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets) + public Task SendPacketsAsync(TimeSpan timeout, IEnumerable packets, CancellationToken cancellationToken) { ThrowIfPartnerIsNull(); diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 77b9ea6e..4032a173 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -19,6 +19,7 @@ namespace MQTTnet.TestApp.NetCore Console.WriteLine("2 = Start server"); Console.WriteLine("3 = Start performance test"); Console.WriteLine("4 = Start managed client"); + Console.WriteLine("5 = Start public broker test"); var pressedKey = Console.ReadKey(true); if (pressedKey.KeyChar == '1') @@ -37,6 +38,10 @@ namespace MQTTnet.TestApp.NetCore { Task.Run(ManagedClientTest.RunAsync); } + else if (pressedKey.KeyChar == '5') + { + Task.Run(PublicBrokerTest.RunAsync); + } Thread.Sleep(Timeout.Infinite); } diff --git a/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs b/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs new file mode 100644 index 00000000..1265a6a2 --- /dev/null +++ b/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs @@ -0,0 +1,128 @@ +using MQTTnet.Client; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Protocol; +using Newtonsoft.Json; + +namespace MQTTnet.TestApp.NetCore +{ + public static class PublicBrokerTest + { + public static async Task RunAsync() + { + //MqttNetGlobalLogger.LogMessagePublished += (s, e) => Console.WriteLine(e.TraceMessage); + + // iot.eclipse.org + await ExecuteTestAsync("iot.eclipse.org TCP", + new MqttClientOptionsBuilder().WithTcpServer("iot.eclipse.org", 1883).Build()); + + await ExecuteTestAsync("iot.eclipse.org WS", + new MqttClientOptionsBuilder().WithWebSocketServer("iot.eclipse.org:80/mqtt").Build()); + + await ExecuteTestAsync("iot.eclipse.org WS TLS", + new MqttClientOptionsBuilder().WithWebSocketServer("iot.eclipse.org:443/mqtt").WithTls().Build()); + + // test.mosquitto.org + await ExecuteTestAsync("test.mosquitto.org TCP", + new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).Build()); + + await ExecuteTestAsync("test.mosquitto.org TCP TLS", + new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithTls().Build()); + + await ExecuteTestAsync("test.mosquitto.org WS", + new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").Build()); + + await ExecuteTestAsync("test.mosquitto.org WS TLS", + new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").Build()); + + // broker.hivemq.com + await ExecuteTestAsync("broker.hivemq.com TCP", + new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).Build()); + + await ExecuteTestAsync("broker.hivemq.com WS", + new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build()); + + // mqtt.swifitch.cz + await ExecuteTestAsync("mqtt.swifitch.cz", + new MqttClientOptionsBuilder().WithTcpServer("mqtt.swifitch.cz", 1883).Build()); + + // CloudMQTT + var configFile = Path.Combine("E:\\CloudMqttTestConfig.json"); + if (File.Exists(configFile)) + { + var config = JsonConvert.DeserializeObject(File.ReadAllText(configFile)); + + await ExecuteTestAsync("CloudMQTT TCP", + new MqttClientOptionsBuilder().WithTcpServer(config.Server, config.Port).WithCredentials(config.Username, config.Password).Build()); + + await ExecuteTestAsync("CloudMQTT TCP TLS", + new MqttClientOptionsBuilder().WithTcpServer(config.Server, config.SslPort).WithCredentials(config.Username, config.Password).WithTls().Build()); + + await ExecuteTestAsync("CloudMQTT WS TLS", + new MqttClientOptionsBuilder().WithWebSocketServer(config.Server + ":" + config.SslWebSocketPort + "/mqtt").WithCredentials(config.Username, config.Password).WithTls().Build()); + } + + Write("Finished.", ConsoleColor.White); + Console.ReadLine(); + } + + private static async Task ExecuteTestAsync(string name, IMqttClientOptions options) + { + try + { + Write("Testing '" + name + "'... ", ConsoleColor.Gray); + var factory = new MqttFactory(); + var client = factory.CreateMqttClient(); + var topic = Guid.NewGuid().ToString(); + + MqttApplicationMessage receivedMessage = null; + client.ApplicationMessageReceived += (s, e) => receivedMessage = e.ApplicationMessage; + + await client.ConnectAsync(options); + await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce); + await client.PublishAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce); + + SpinWait.SpinUntil(() => receivedMessage != null, 5000); + + if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World") + { + throw new Exception("Message invalid."); + } + + await client.UnsubscribeAsync("test"); + await client.DisconnectAsync(); + + Write("[OK]\n", ConsoleColor.Green); + } + catch (Exception e) + { + Write("[FAILED] " + e.Message + "\n", ConsoleColor.Red); + } + } + + private static void Write(string message, ConsoleColor color) + { + Console.ForegroundColor = color; + Console.Write(message); + } + + public class MqttConfig + { + public string Server { get; set; } + + public string Username { get; set; } + + public string Password { get; set; } + + public int Port { get; set; } + + public int SslPort { get; set; } + + public int WebSocketPort { get; set; } + + public int SslWebSocketPort { get; set; } + } + } +} From 84dab18c2a11a3679b777c94f68a6486a8728460 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 25 Apr 2018 19:55:30 +0200 Subject: [PATCH 023/138] Fix broken UWP support. --- .../Implementations/MqttTcpChannel.Uwp.cs | 11 +++++++++-- .../Implementations/MqttTcpServerAdapter.Uwp.cs | 8 +++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs index 1487f0a7..41e1d54a 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs @@ -37,6 +37,8 @@ namespace MQTTnet.Implementations public MqttTcpChannel(StreamSocket socket) { _socket = socket ?? throw new ArgumentNullException(nameof(socket)); + + CreateStreams(); } public static Func> CustomIgnorableServerCertificateErrorsResolver { get; set; } @@ -65,8 +67,7 @@ namespace MQTTnet.Implementations await _socket.ConnectAsync(new HostName(_options.Server), _options.GetPort().ToString(), SocketProtectionLevel.Tls12); } - _readStream = _socket.InputStream.AsStreamForRead(_bufferSize); - _writeStream = _socket.OutputStream.AsStreamForWrite(_bufferSize); + CreateStreams(); } public Task DisconnectAsync() @@ -177,6 +178,12 @@ namespace MQTTnet.Implementations return result; } + + private void CreateStreams() + { + _readStream = _socket.InputStream.AsStreamForRead(_bufferSize); + _writeStream = _socket.OutputStream.AsStreamForWrite(_bufferSize); + } } } #endif \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs index c6b0ff45..aba092af 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs @@ -26,10 +26,14 @@ namespace MQTTnet.Implementations if (options == null) throw new ArgumentNullException(nameof(options)); if (_defaultEndpointSocket != null) throw new InvalidOperationException("Server is already started."); - + if (options.DefaultEndpointOptions.IsEnabled) { _defaultEndpointSocket = new StreamSocketListener(); + + // This also affects the client sockets. + _defaultEndpointSocket.Control.NoDelay = true; + await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket); _defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync; } @@ -62,8 +66,6 @@ namespace MQTTnet.Implementations { try { - args.Socket.Control.NoDelay = true; - var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } From 74a552296510747f662aa08b92f2a76da03b0ea1 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 25 Apr 2018 20:22:25 +0200 Subject: [PATCH 024/138] Add topic filter tests from MQTT spec --- .../Adapter/MqttChannelAdapter.cs | 2 +- .../TopicFilterComparerTests.cs | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 894b8d97..c531adde 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -129,7 +129,7 @@ namespace MQTTnet.Adapter { return null; } - + if (header.BodyLength == 0) { return new ReceivedMqttPacket(header, new MemoryStream(new byte[0], false)); diff --git a/Tests/MQTTnet.Core.Tests/TopicFilterComparerTests.cs b/Tests/MQTTnet.Core.Tests/TopicFilterComparerTests.cs index be169212..e91e9885 100644 --- a/Tests/MQTTnet.Core.Tests/TopicFilterComparerTests.cs +++ b/Tests/MQTTnet.Core.Tests/TopicFilterComparerTests.cs @@ -60,7 +60,33 @@ namespace MQTTnet.Core.Tests CompareAndAssert("A/B/C/D", "#", true); } - private void CompareAndAssert(string topic, string filter, bool expectedResult) + [TestMethod] + public void TopicFilterComparer_MultiLevel_Sport() + { + // Tests from official MQTT spec (4.7.1.2 Multi-level wildcard) + CompareAndAssert("sport/tennis/player1", "sport/tennis/player1/#", true); + CompareAndAssert("sport/tennis/player1/ranking", "sport/tennis/player1/#", true); + CompareAndAssert("sport/tennis/player1/score/wimbledon", "sport/tennis/player1/#", true); + + CompareAndAssert("sport/tennis/player1", "sport/tennis/+", true); + CompareAndAssert("sport/tennis/player2", "sport/tennis/+", true); + CompareAndAssert("sport/tennis/player1/ranking", "sport/tennis/+", false); + + CompareAndAssert("sport", "sport/#", true); + CompareAndAssert("sport", "sport/+", false); + CompareAndAssert("sport/", "sport/+", true); + } + + [TestMethod] + public void TopicFilterComparer_SingleLevel_Finance() + { + // Tests from official MQTT spec (4.7.1.3 Single level wildcard) + CompareAndAssert("/finance", "+/+", true); + CompareAndAssert("/finance", "/+", true); + CompareAndAssert("/finance", "+", false); + } + + private static void CompareAndAssert(string topic, string filter, bool expectedResult) { Assert.AreEqual(expectedResult, MqttTopicFilterComparer.IsMatch(topic, filter)); } From 357fd844d7230f82c9f2af98ccf46dac7343386b Mon Sep 17 00:00:00 2001 From: Christian Date: Sat, 28 Apr 2018 13:05:22 +0200 Subject: [PATCH 025/138] Fix TopicFilterComparer --- .../Server/MqttTopicFilterComparer.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs index 82444159..00e0bd65 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs @@ -19,12 +19,17 @@ namespace MQTTnet.Server var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None); var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None); + // # > In either case it MUST be the last character specified in the Topic Filter [MQTT-4.7.1-2]. for (var i = 0; i < fragmentsFilter.Length; i++) { - switch (fragmentsFilter[i]) + if (fragmentsFilter[i] == "+") { - case "+": continue; - case "#" when i == fragmentsFilter.Length - 1: return true; + continue; + } + + if (fragmentsFilter[i] == "#") + { + return true; } if (i >= fragmentsTopic.Length) @@ -38,7 +43,7 @@ namespace MQTTnet.Server } } - return fragmentsTopic.Length <= fragmentsFilter.Length; + return fragmentsTopic.Length == fragmentsFilter.Length; } } } From b9a73cc23dad056390ce3d1ca7299e980cebb072 Mon Sep 17 00:00:00 2001 From: Christian Date: Sat, 28 Apr 2018 13:05:35 +0200 Subject: [PATCH 026/138] Changed socket features. --- .../Implementations/MqttTcpChannel.Uwp.cs | 1 + .../Implementations/MqttTcpServerAdapter.Uwp.cs | 7 +++++-- .../Implementations/MqttTcpServerAdapter.cs | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs index 41e1d54a..19d58240 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs @@ -49,6 +49,7 @@ namespace MQTTnet.Implementations { _socket = new StreamSocket(); _socket.Control.NoDelay = true; + _socket.Control.KeepAlive = true; } if (!_options.TlsOptions.UseTls) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs index aba092af..b2c31103 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs @@ -33,9 +33,12 @@ namespace MQTTnet.Implementations // This also affects the client sockets. _defaultEndpointSocket.Control.NoDelay = true; + _defaultEndpointSocket.Control.KeepAlive = true; + _defaultEndpointSocket.Control.QualityOfService = SocketQualityOfService.LowLatency; + _defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync; await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket); - _defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync; + } if (options.TlsEndpointOptions.IsEnabled) @@ -59,7 +62,7 @@ namespace MQTTnet.Implementations public void Dispose() { - StopAsync(); + StopAsync().GetAwaiter().GetResult(); } private void AcceptDefaultEndpointConnectionsAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs index 56457b52..f1d349d1 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs @@ -88,7 +88,7 @@ namespace MQTTnet.Implementations public void Dispose() { - StopAsync(); + StopAsync().GetAwaiter().GetResult(); } private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken) From 49cf36435203c27d2cee795e0cd27e8c479c36be Mon Sep 17 00:00:00 2001 From: Christian Date: Sat, 28 Apr 2018 13:05:45 +0200 Subject: [PATCH 027/138] Update docs --- Build/MQTTnet.nuspec | 5 +++-- README.md | 2 +- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 4fb8719b..30bf138d 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -10,11 +10,12 @@ https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png false MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). - * [Client] Added new overloads for the message builder. + * [Client] Added new overloads for the message builders. * [Core] Performance optimizations (thanks to @ israellot) * [Core] Fixed a memory leak which was caused by not properly stopped async tasks. * [Client] Fixed a deadlock when connecting to a not reachable server. -* [Server] Fixed a deadlock when reusing the same _ClientId_ while a will message is used (thanks to @william-wps). +* [Server] Fixed a deadlock when reusing the same _ClientId_ while a _will message_ is used (thanks to @william-wps). +* [Server] Fixed wrong topic filter matching for filters like 'sport/+' which should _not_ match 'sport'. Copyright Christian Kratky 2016-2018 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 diff --git a/README.md b/README.md index 409cbae2..cff5490f 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ If you use this library and want to see your project here please let me know. ## MIT License -Copyright (c) 2017 Christian Kratky +Copyright (c) 2017-2018 Christian Kratky Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index dc2f5251..399e71ed 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -244,8 +244,9 @@ namespace MQTTnet.Core.Tests var c2 = await serverAdapter.ConnectTestClient(s, "c2"); c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; + + await Task.Delay(200); await c2.SubscribeAsync(new TopicFilter("retained", MqttQualityOfServiceLevel.AtMostOnce)); - await Task.Delay(500); } finally From 62527a91ac172a8cc3e020dbfe228f8112c7df85 Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 3 May 2018 20:44:41 +0200 Subject: [PATCH 028/138] Complete merge --- .../MQTTnet.NetStandard/Client/MqttClient.cs | 8 +++--- .../Server/MqttClientKeepAliveMonitor.cs | 9 +++---- .../Server/MqttClientPendingMessagesQueue.cs | 2 +- .../Server/MqttClientSession.cs | 27 ++++++++++--------- .../Server/MqttClientSessionsManager.cs | 6 ++--- .../MqttKeepAliveMonitorTests.cs | 3 --- 6 files changed, 26 insertions(+), 29 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index 4c264f96..11d7111a 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -291,13 +291,13 @@ namespace MQTTnet.Client private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken) { _sendTracker.Restart(); - return _adapter.SendPacketsAsync(_options.CommunicationTimeout, cancellationToken, new[] { packet }); + return _adapter.SendPacketsAsync(_options.CommunicationTimeout, new[] { packet }, cancellationToken); } private Task SendAsync(IEnumerable packets, CancellationToken cancellationToken) { _sendTracker.Restart(); - return _adapter.SendPacketsAsync(_options.CommunicationTimeout, cancellationToken, packets); + return _adapter.SendPacketsAsync(_options.CommunicationTimeout, packets, cancellationToken); } private async Task SendAndReceiveAsync(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket @@ -313,8 +313,8 @@ namespace MQTTnet.Client var packetAwaiter = _packetDispatcher.AddPacketAwaiter(identifier); try { - await _adapter.SendPacketsAsync(_options.CommunicationTimeout, new[] { requestPacket }, _cancellationTokenSource.Token).ConfigureAwait(false); - var respone = await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, _options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false); + await _adapter.SendPacketsAsync(_options.CommunicationTimeout, new[] { requestPacket }, cancellationToken).ConfigureAwait(false); + var respone = await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, _options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); return (TResponsePacket)respone; } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs index 3f215369..8dc96e40 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs @@ -13,12 +13,12 @@ namespace MQTTnet.Server private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch(); private readonly string _clientId; - private readonly Func _timeoutCallback; + private readonly Action _timeoutCallback; private readonly IMqttNetLogger _logger; private Task _workerTask; - public MqttClientKeepAliveMonitor(string clientId, Func timeoutCallback, IMqttNetLogger logger) + public MqttClientKeepAliveMonitor(string clientId, Action timeoutCallback, IMqttNetLogger logger) { _clientId = clientId; _timeoutCallback = timeoutCallback; @@ -61,10 +61,7 @@ namespace MQTTnet.Server { _logger.Warning("Client '{0}': Did not receive any packet or keep alive signal.", _clientId); - if (_timeoutCallback != null) - { - await _timeoutCallback().ConfigureAwait(false); - } + _timeoutCallback?.Invoke(); return; } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs index 8c98ac3f..f56a8c91 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs @@ -127,7 +127,7 @@ namespace MQTTnet.Server if (!cancellationToken.IsCancellationRequested) { - await _clientSession.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); + _clientSession.Stop(MqttClientDisconnectType.NotClean); } } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs index 3b51e9c9..0e61b107 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs @@ -39,7 +39,7 @@ namespace MQTTnet.Server ClientId = clientId; - KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeoutAsync, _logger); + KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeout, _logger); SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server); PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); } @@ -101,13 +101,13 @@ namespace MQTTnet.Server return _wasCleanDisconnect; } - public async Task StopAsync(MqttClientDisconnectType type) + public void Stop(MqttClientDisconnectType type) { try { if (_cancellationTokenSource == null) { - return Task.FromResult(0); + return; } _wasCleanDisconnect = type == MqttClientDisconnectType.Clean; @@ -128,8 +128,6 @@ namespace MQTTnet.Server { _logger.Info("Client '{0}': Session stopped.", ClientId); } - - return Task.FromResult(0); } public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage) @@ -183,10 +181,10 @@ namespace MQTTnet.Server _cancellationTokenSource?.Dispose(); } - private Task StopDueToKeepAliveTimeoutAsync() + private void StopDueToKeepAliveTimeout() { _logger.Info("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId); - return StopAsync(MqttClientDisconnectType.NotClean); + Stop(MqttClientDisconnectType.NotClean); } private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) @@ -214,7 +212,7 @@ namespace MQTTnet.Server _logger.Error(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); } - await StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); + Stop(MqttClientDisconnectType.NotClean); } } @@ -263,16 +261,20 @@ namespace MQTTnet.Server if (packet is MqttDisconnectPacket) { - return StopAsync(MqttClientDisconnectType.Clean); + Stop(MqttClientDisconnectType.Clean); + return Task.FromResult(0); } if (packet is MqttConnectPacket) { - return StopAsync(MqttClientDisconnectType.NotClean); + Stop(MqttClientDisconnectType.NotClean); + return Task.FromResult(0); } _logger.Warning("Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); - return StopAsync(MqttClientDisconnectType.NotClean); + Stop(MqttClientDisconnectType.NotClean); + + return Task.FromResult(0); } private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters) @@ -291,7 +293,8 @@ namespace MQTTnet.Server if (subscribeResult.CloseConnection) { - await StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); + Stop(MqttClientDisconnectType.NotClean); + return; } await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false); diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index 9302f0ae..98e58350 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -120,7 +120,7 @@ namespace MQTTnet.Server { foreach (var session in _sessions) { - await session.Value.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false); + session.Value.Stop(MqttClientDisconnectType.NotClean); } _sessions.Clear(); @@ -232,7 +232,7 @@ namespace MQTTnet.Server { _sessions.Remove(connectPacket.ClientId); - await clientSession.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false); + clientSession.Stop(MqttClientDisconnectType.Clean); clientSession.Dispose(); clientSession = null; @@ -270,7 +270,7 @@ namespace MQTTnet.Server var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage); if (interceptorContext.CloseConnection) { - await senderClientSession.StopAsync().ConfigureAwait(false); + senderClientSession.Stop(MqttClientDisconnectType.NotClean); } if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish) diff --git a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs index 206942f4..f3c43203 100644 --- a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs @@ -1,5 +1,4 @@ using System.Threading; -using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Diagnostics; using MQTTnet.Packets; @@ -18,7 +17,6 @@ namespace MQTTnet.Core.Tests var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate { timeoutCalledCount++; - return Task.FromResult(0); }, new MqttNetLogger()); Assert.AreEqual(0, timeoutCalledCount); @@ -40,7 +38,6 @@ namespace MQTTnet.Core.Tests var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate { timeoutCalledCount++; - return Task.FromResult(0); }, new MqttNetLogger()); Assert.AreEqual(0, timeoutCalledCount); From d14088512bdafd8b51d3c290f91f7fc2b67fe66f Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 3 May 2018 20:47:15 +0200 Subject: [PATCH 029/138] Refactoring --- Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index 11d7111a..2e04c906 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -50,8 +50,8 @@ namespace MQTTnet.Client try { - _options = options; _cancellationTokenSource = new CancellationTokenSource(); + _options = options; _packetIdentifierProvider.Reset(); _packetDispatcher.Reset(); @@ -212,7 +212,7 @@ namespace MQTTnet.Client WillMessage = willApplicationMessage }; - var response = await SendAndReceiveAsync(connectPacket, _cancellationTokenSource.Token).ConfigureAwait(false); + var response = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false); if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { throw new MqttConnectingFailedException(response.ConnectReturnCode); From b22b02a0b6db39e0674bbf27313bce7eabe881f8 Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 3 May 2018 22:22:07 +0200 Subject: [PATCH 030/138] Refactor subscriptions manager --- .../MQTTnet.NetStandard/Client/MqttClient.cs | 15 +++- .../Internal/AsyncAutoResetEvent.cs | 26 +++++++ .../MQTTnet.NetStandard/Internal/AsyncLock.cs | 26 +++++++ .../Server/MqttClientPendingMessagesQueue.cs | 13 ++-- .../Server/MqttClientSession.cs | 14 ++-- .../Server/MqttClientSubscriptionsManager.cs | 67 +++++++--------- .../AsyncAutoResentEventTests.cs | 34 ++++++++ Tests/MQTTnet.Core.Tests/AsyncLockTests.cs | 44 +++++++++++ Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 77 ++++++++++++++----- .../MqttSubscriptionsManagerTests.cs | 12 +-- .../TestMqttCommunicationAdapter.cs | 23 +++++- .../TestMqttServerAdapter.cs | 25 +----- 12 files changed, 269 insertions(+), 107 deletions(-) create mode 100644 Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs create mode 100644 Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs create mode 100644 Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs create mode 100644 Tests/MQTTnet.Core.Tests/AsyncLockTests.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index 2e04c906..8384bca6 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -290,18 +290,27 @@ namespace MQTTnet.Client private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken) { - _sendTracker.Restart(); - return _adapter.SendPacketsAsync(_options.CommunicationTimeout, new[] { packet }, cancellationToken); + return SendAsync(new[] { packet }, cancellationToken); } private Task SendAsync(IEnumerable packets, CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + throw new TaskCanceledException(); + } + _sendTracker.Restart(); return _adapter.SendPacketsAsync(_options.CommunicationTimeout, packets, cancellationToken); } private async Task SendAndReceiveAsync(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket { + if (cancellationToken.IsCancellationRequested) + { + throw new TaskCanceledException(); + } + _sendTracker.Restart(); ushort identifier = 0; @@ -528,7 +537,7 @@ namespace MQTTnet.Client private static async Task WaitForTaskAsync(Task task, Task sender) { - if (task == sender) + if (task == sender || task == null) { return; } diff --git a/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs b/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs new file mode 100644 index 00000000..ad20646c --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MQTTnet.Internal +{ + public sealed class AsyncAutoResetEvent : IDisposable + { + private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0, 1); + + public Task WaitOneAsync(CancellationToken cancellationToken) + { + return _semaphore.WaitAsync(cancellationToken); + } + + public void Set() + { + _semaphore.Release(); + } + + public void Dispose() + { + _semaphore?.Dispose(); + } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs b/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs new file mode 100644 index 00000000..145e385f --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MQTTnet.Internal +{ + public sealed class AsyncLock : IDisposable + { + private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); + + public Task EnterAsync(CancellationToken cancellationToken) + { + return _semaphore.WaitAsync(cancellationToken); + } + + public void Exit() + { + _semaphore.Release(); + } + + public void Dispose() + { + _semaphore?.Dispose(); + } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs index f56a8c91..1d005285 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; +using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -13,7 +14,7 @@ namespace MQTTnet.Server public sealed class MqttClientPendingMessagesQueue : IDisposable { private readonly ConcurrentQueue _queue = new ConcurrentQueue(); - private readonly SemaphoreSlim _queueWaitSemaphore = new SemaphoreSlim(0); + private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent(); private readonly IMqttServerOptions _options; private readonly MqttClientSession _clientSession; private readonly IMqttNetLogger _logger; @@ -54,7 +55,7 @@ namespace MQTTnet.Server if (packet == null) throw new ArgumentNullException(nameof(packet)); _queue.Enqueue(packet); - _queueWaitSemaphore.Release(); + _queueAutoResetEvent.Set(); _logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId); } @@ -82,7 +83,7 @@ namespace MQTTnet.Server MqttBasePacket packet = null; try { - await _queueWaitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + await _queueAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false); if (!_queue.TryDequeue(out packet)) { throw new InvalidOperationException(); // should not happen @@ -120,8 +121,8 @@ namespace MQTTnet.Server if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { publishPacket.Dup = true; - _queue.Enqueue(packet); - _queueWaitSemaphore.Release(); + + Enqueue(publishPacket); } } @@ -134,7 +135,7 @@ namespace MQTTnet.Server public void Dispose() { - _queueWaitSemaphore?.Dispose(); + _queueAutoResetEvent?.Dispose(); } } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs index 0e61b107..183ab8e5 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs @@ -134,7 +134,7 @@ namespace MQTTnet.Server { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage); + var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage).ConfigureAwait(false); if (!result.IsSubscribed) { return; @@ -155,10 +155,10 @@ namespace MQTTnet.Server { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - await SubscriptionsManager.SubscribeAsync(new MqttSubscribePacket + SubscriptionsManager.Subscribe(new MqttSubscribePacket { TopicFilters = topicFilters - }).ConfigureAwait(false); + }); await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false); } @@ -167,10 +167,12 @@ namespace MQTTnet.Server { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - return SubscriptionsManager.UnsubscribeAsync(new MqttUnsubscribePacket + SubscriptionsManager.Unsubscribe(new MqttUnsubscribePacket { TopicFilters = topicFilters }); + + return Task.FromResult(0); } public void Dispose() @@ -288,7 +290,7 @@ namespace MQTTnet.Server private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) { - var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false); + var subscribeResult = SubscriptionsManager.Subscribe(subscribePacket); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { subscribeResult.ResponsePacket }, cancellationToken).ConfigureAwait(false); if (subscribeResult.CloseConnection) @@ -302,7 +304,7 @@ namespace MQTTnet.Server private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) { - var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false); + var unsubscribeResult = SubscriptionsManager.Unsubscribe(unsubscribePacket); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { unsubscribeResult }, cancellationToken); } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs index a49723af..dfb94638 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -10,7 +11,7 @@ namespace MQTTnet.Server { public sealed class MqttClientSubscriptionsManager : IDisposable { - private readonly Dictionary _subscriptions = new Dictionary(); + private readonly ConcurrentDictionary _subscriptions = new ConcurrentDictionary(); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly IMqttServerOptions _options; private readonly MqttServer _server; @@ -23,7 +24,7 @@ namespace MQTTnet.Server _server = server; } - public async Task SubscribeAsync(MqttSubscribePacket subscribePacket) + public MqttClientSubscribeResult Subscribe(MqttSubscribePacket subscribePacket) { if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); @@ -37,57 +38,41 @@ namespace MQTTnet.Server CloseConnection = false }; - await _semaphore.WaitAsync().ConfigureAwait(false); - try + foreach (var topicFilter in subscribePacket.TopicFilters) { - foreach (var topicFilter in subscribePacket.TopicFilters) + var interceptorContext = InterceptSubscribe(topicFilter); + if (!interceptorContext.AcceptSubscription) { - var interceptorContext = InterceptSubscribe(topicFilter); - if (!interceptorContext.AcceptSubscription) - { - result.ResponsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure); - } - else - { - result.ResponsePacket.SubscribeReturnCodes.Add(ConvertToMaximumQoS(topicFilter.QualityOfServiceLevel)); - } - - if (interceptorContext.CloseConnection) - { - result.CloseConnection = true; - } - - if (interceptorContext.AcceptSubscription) - { - _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; - _server.OnClientSubscribedTopic(_clientId, topicFilter); - } + result.ResponsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure); + } + else + { + result.ResponsePacket.SubscribeReturnCodes.Add(ConvertToMaximumQoS(topicFilter.QualityOfServiceLevel)); + } + + if (interceptorContext.CloseConnection) + { + result.CloseConnection = true; + } + + if (interceptorContext.AcceptSubscription) + { + _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; + _server.OnClientSubscribedTopic(_clientId, topicFilter); } - } - finally - { - _semaphore.Release(); } return result; } - public async Task UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket) + public MqttUnsubAckPacket Unsubscribe(MqttUnsubscribePacket unsubscribePacket) { if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); - await _semaphore.WaitAsync().ConfigureAwait(false); - try + foreach (var topicFilter in unsubscribePacket.TopicFilters) { - foreach (var topicFilter in unsubscribePacket.TopicFilters) - { - _subscriptions.Remove(topicFilter); - _server.OnClientUnsubscribedTopic(_clientId, topicFilter); - } - } - finally - { - _semaphore.Release(); + _subscriptions.TryRemove(topicFilter, out _); + _server.OnClientUnsubscribedTopic(_clientId, topicFilter); } return new MqttUnsubAckPacket diff --git a/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs b/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs new file mode 100644 index 00000000..77275eb9 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs @@ -0,0 +1,34 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Internal; + +namespace MQTTnet.Core.Tests +{ + [TestClass] + public class AsyncAutoResetEventTests + { + [TestMethod] + public async Task AsyncAutoResetEvent() + { + var aare = new AsyncAutoResetEvent(); + + var increment = 0; + var globalI = 0; +#pragma warning disable 4014 + Task.Run(async () => +#pragma warning restore 4014 + { + await aare.WaitOneAsync(CancellationToken.None); + globalI += increment; + }); + + await Task.Delay(500); + increment = 1; + aare.Set(); + await Task.Delay(100); + + Assert.AreEqual(1, globalI); + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs b/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs new file mode 100644 index 00000000..56c70500 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs @@ -0,0 +1,44 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Internal; + +namespace MQTTnet.Core.Tests +{ + [TestClass] + public class AsyncLockTests + { + [TestMethod] + public void AsyncLock() + { + const int ThreadsCount = 10; + + var threads = new Task[ThreadsCount]; + var @lock = new AsyncLock(); + var globalI = 0; + for (var i = 0; i < ThreadsCount; i++) + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + threads[i] = Task.Run(async () => + { + await @lock.EnterAsync(CancellationToken.None); + try + { + var localI = globalI; + await Task.Delay(10); // Increase the chance for wrong data. + localI++; + globalI = localI; + } + finally + { + @lock.Exit(); + } + }); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + + Task.WaitAll(threads); + Assert.AreEqual(ThreadsCount, globalI); + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index 399e71ed..bcd9fb85 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Diagnostics; @@ -58,8 +59,8 @@ namespace MQTTnet.Core.Tests await s.StartAsync(new MqttServerOptions()); var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build(); - var c1 = await serverAdapter.ConnectTestClient(s, "c1"); - var c2 = await serverAdapter.ConnectTestClient(s, "c2", willMessage); + var c1 = await serverAdapter.ConnectTestClient("c1"); + var c2 = await serverAdapter.ConnectTestClient("c2", willMessage); c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build()); @@ -90,8 +91,8 @@ namespace MQTTnet.Core.Tests { await s.StartAsync(new MqttServerOptions()); - var c1 = await serverAdapter.ConnectTestClient(s, "c1"); - var c2 = await serverAdapter.ConnectTestClient(s, "c2"); + var c1 = await serverAdapter.ConnectTestClient("c1"); + var c2 = await serverAdapter.ConnectTestClient("c2"); c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); @@ -149,7 +150,7 @@ namespace MQTTnet.Core.Tests { await s.StartAsync(new MqttServerOptions()); - var c1 = await serverAdapter.ConnectTestClient(s, "c1"); + var c1 = await serverAdapter.ConnectTestClient("c1"); c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; @@ -167,6 +168,40 @@ namespace MQTTnet.Core.Tests Assert.AreEqual(1, receivedMessagesCount); } + [TestMethod] + public async Task MqttServer_RetainedMessagesFlow() + { + var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build(); + var serverAdapter = new TestMqttServerAdapter(); + var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); + await s.StartAsync(new MqttServerOptions()); + var c1 = await serverAdapter.ConnectTestClient("c1"); + await c1.PublishAsync(retainedMessage); + Thread.Sleep(500); + await c1.DisconnectAsync(); + Thread.Sleep(500); + + var receivedMessages = 0; + var c2 = await serverAdapter.ConnectTestClient("c2"); + c2.ApplicationMessageReceived += (_, e) => + { + receivedMessages++; + }; + + for (var i = 0; i < 5; i++) + { + await c2.UnsubscribeAsync("r"); + await Task.Delay(500); + Assert.AreEqual(i, receivedMessages); + + await c2.SubscribeAsync("r"); + await Task.Delay(500); + Assert.AreEqual(i + 1, receivedMessages); + } + + await c2.DisconnectAsync(); + } + [TestMethod] public async Task MqttServer_NoRetainedMessage() { @@ -179,11 +214,11 @@ namespace MQTTnet.Core.Tests { await s.StartAsync(new MqttServerOptions()); - var c1 = await serverAdapter.ConnectTestClient(s, "c1"); + var c1 = await serverAdapter.ConnectTestClient("c1"); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).Build()); await c1.DisconnectAsync(); - var c2 = await serverAdapter.ConnectTestClient(s, "c2"); + var c2 = await serverAdapter.ConnectTestClient("c2"); c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); @@ -208,11 +243,11 @@ namespace MQTTnet.Core.Tests { await s.StartAsync(new MqttServerOptions()); - var c1 = await serverAdapter.ConnectTestClient(s, "c1"); + var c1 = await serverAdapter.ConnectTestClient("c1"); await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); await c1.DisconnectAsync(); - var c2 = await serverAdapter.ConnectTestClient(s, "c2"); + var c2 = await serverAdapter.ConnectTestClient("c2"); c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); @@ -237,14 +272,14 @@ namespace MQTTnet.Core.Tests { await s.StartAsync(new MqttServerOptions()); - var c1 = await serverAdapter.ConnectTestClient(s, "c1"); + var c1 = await serverAdapter.ConnectTestClient("c1"); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build()); await c1.DisconnectAsync(); - - var c2 = await serverAdapter.ConnectTestClient(s, "c2"); + + var c2 = await serverAdapter.ConnectTestClient("c2"); c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; - + await Task.Delay(200); await c2.SubscribeAsync(new TopicFilter("retained", MqttQualityOfServiceLevel.AtMostOnce)); await Task.Delay(500); @@ -271,7 +306,7 @@ namespace MQTTnet.Core.Tests await s.StartAsync(options); - var c1 = await serverAdapter.ConnectTestClient(s, "c1"); + var c1 = await serverAdapter.ConnectTestClient("c1"); await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); await c1.DisconnectAsync(); @@ -291,7 +326,7 @@ namespace MQTTnet.Core.Tests var options = new MqttServerOptions { Storage = storage }; await s.StartAsync(options); - var c2 = await serverAdapter.ConnectTestClient(s, "c2"); + var c2 = await serverAdapter.ConnectTestClient("c2"); c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); @@ -322,8 +357,8 @@ namespace MQTTnet.Core.Tests await s.StartAsync(options); - var c1 = await serverAdapter.ConnectTestClient(s, "c1"); - var c2 = await serverAdapter.ConnectTestClient(s, "c2"); + var c1 = await serverAdapter.ConnectTestClient("c1"); + var c2 = await serverAdapter.ConnectTestClient("c2"); await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("test").Build()); var isIntercepted = false; @@ -357,8 +392,8 @@ namespace MQTTnet.Core.Tests { await s.StartAsync(new MqttServerOptions()); - var c1 = await serverAdapter.ConnectTestClient(s, "c1"); - var c2 = await serverAdapter.ConnectTestClient(s, "c2"); + var c1 = await serverAdapter.ConnectTestClient("c1"); + var c2 = await serverAdapter.ConnectTestClient("c2"); c1.ApplicationMessageReceived += (_, e) => { @@ -412,8 +447,8 @@ namespace MQTTnet.Core.Tests { await s.StartAsync(new MqttServerOptions()); - var c1 = await serverAdapter.ConnectTestClient(s, "c1"); - var c2 = await serverAdapter.ConnectTestClient(s, "c2"); + var c1 = await serverAdapter.ConnectTestClient("c1"); + var c2 = await serverAdapter.ConnectTestClient("c2"); c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; diff --git a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs index 13464155..6f16ab5d 100644 --- a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs @@ -18,7 +18,7 @@ namespace MQTTnet.Core.Tests var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); - sm.SubscribeAsync(sp).Wait(); + sm.Subscribe(sp); var pp = new MqttApplicationMessage { @@ -39,7 +39,7 @@ namespace MQTTnet.Core.Tests var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtMostOnce)); - sm.SubscribeAsync(sp).Wait(); + sm.Subscribe(sp); var pp = new MqttApplicationMessage { @@ -61,7 +61,7 @@ namespace MQTTnet.Core.Tests sp.TopicFilters.Add(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)); sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtLeastOnce)); - sm.SubscribeAsync(sp).Wait(); + sm.Subscribe(sp); var pp = new MqttApplicationMessage { @@ -82,7 +82,7 @@ namespace MQTTnet.Core.Tests var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); - sm.SubscribeAsync(sp).Wait(); + sm.Subscribe(sp); var pp = new MqttApplicationMessage { @@ -101,7 +101,7 @@ namespace MQTTnet.Core.Tests var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); - sm.SubscribeAsync(sp).Wait(); + sm.Subscribe(sp); var pp = new MqttApplicationMessage { @@ -113,7 +113,7 @@ namespace MQTTnet.Core.Tests var up = new MqttUnsubscribePacket(); up.TopicFilters.Add("A/B/C"); - sm.UnsubscribeAsync(up).Wait(); + sm.Unsubscribe(up); Assert.IsFalse(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed); } diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index 808e08a3..837221b1 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -43,11 +43,30 @@ namespace MQTTnet.Core.Tests return Task.FromResult(0); } - public Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) + public async Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) { ThrowIfPartnerIsNull(); - return Task.Run(() => + if (timeout > TimeSpan.Zero) + { + using (var timeoutCts = new CancellationTokenSource(timeout)) + using (var cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken)) + { + return await Task.Run(() => + { + try + { + return _incomingPackets.Take(cts.Token); + } + catch + { + return null; + } + }, cts.Token); + } + } + + return await Task.Run(() => { try { diff --git a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs index 8b9d0ba0..2b24f42b 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Client; @@ -11,7 +12,7 @@ namespace MQTTnet.Core.Tests { public event EventHandler ClientAccepted; - public async Task ConnectTestClient(IMqttServer server, string clientId, MqttApplicationMessage willMessage = null) + public async Task ConnectTestClient(string clientId, MqttApplicationMessage willMessage = null) { var adapterA = new TestMqttCommunicationAdapter(); var adapterB = new TestMqttCommunicationAdapter(); @@ -22,8 +23,6 @@ namespace MQTTnet.Core.Tests new TestMqttCommunicationAdapterFactory(adapterA), new MqttNetLogger()); - var connected = WaitForClientToConnect(server, clientId); - FireClientAcceptedEvent(adapterB); var options = new MqttClientOptions @@ -34,29 +33,11 @@ namespace MQTTnet.Core.Tests }; await client.ConnectAsync(options); - await connected; + SpinWait.SpinUntil(() => client.IsConnected); return client; } - private static Task WaitForClientToConnect(IMqttServer s, string clientId) - { - var tcs = new TaskCompletionSource(); - - void Handler(object sender, Server.MqttClientConnectedEventArgs args) - { - if (args.Client.ClientId == clientId) - { - s.ClientConnected -= Handler; - tcs.SetResult(null); - } - } - - s.ClientConnected += Handler; - - return tcs.Task; - } - private void FireClientAcceptedEvent(IMqttChannelAdapter adapter) { ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(adapter)); From 7720a3f09159253a3e14d27ec608d5464e5762ae Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 10 May 2018 16:27:12 +0200 Subject: [PATCH 031/138] Refactor thread management. --- Build/MQTTnet.nuspec | 2 +- Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs | 12 ++++++++++-- .../Implementations/MqttTcpServerAdapter.cs | 12 ++++++++++-- .../MQTTnet.NetStandard/MQTTnet.Netstandard.csproj | 13 ++++++++----- .../MQTTnet.TestApp.AspNetCore2.csproj | 2 +- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 02f199f7..2be9d25d 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -27,7 +27,7 @@ - + diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index 8384bca6..edf9b440 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -507,12 +507,20 @@ namespace MQTTnet.Client private void StartReceivingPackets(CancellationToken cancellationToken) { - _packetReceiverTask = Task.Run(() => ReceivePacketsAsync(cancellationToken), cancellationToken); + _packetReceiverTask = Task.Factory.StartNew( + () => ReceivePacketsAsync(cancellationToken), + cancellationToken, + TaskCreationOptions.LongRunning, + TaskScheduler.Current); } private void StartSendingKeepAliveMessages(CancellationToken cancellationToken) { - _keepAliveMessageSenderTask = Task.Run(() => SendKeepAliveMessagesAsync(cancellationToken), cancellationToken); + _keepAliveMessageSenderTask = Task.Factory.StartNew( + () => SendKeepAliveMessagesAsync(cancellationToken), + cancellationToken, + TaskCreationOptions.LongRunning, + TaskScheduler.Current); } private void StartProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs index f1d349d1..0404de36 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs @@ -43,7 +43,11 @@ namespace MQTTnet.Implementations _defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort())); _defaultEndpointSocket.Listen(options.ConnectionBacklog); - Task.Run(() => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); + Task.Factory.StartNew( + () => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token), + _cancellationTokenSource.Token, + TaskCreationOptions.LongRunning, + TaskScheduler.Current); } if (options.TlsEndpointOptions.IsEnabled) @@ -63,7 +67,11 @@ namespace MQTTnet.Implementations _tlsEndpointSocket.Bind(new IPEndPoint(options.TlsEndpointOptions.BoundIPAddress, options.GetTlsEndpointPort())); _tlsEndpointSocket.Listen(options.ConnectionBacklog); - Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); + Task.Factory.StartNew( + () => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), + _cancellationTokenSource.Token, + TaskCreationOptions.LongRunning, + TaskScheduler.Current); } return Task.FromResult(0); diff --git a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj index cb154c24..fc1e41af 100644 --- a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj +++ b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj @@ -6,7 +6,6 @@ MQTTnet MQTTnet False - Full 0.0.0.0 0.0.0.0 0.0.0.0 @@ -19,7 +18,7 @@ false - + false UAP,Version=v10.0 UAP @@ -32,6 +31,10 @@ $(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets + + Full + + @@ -43,17 +46,17 @@ - + - + - + diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj index 0a720ca9..29b72538 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj @@ -2,7 +2,7 @@ netcoreapp2.0 - 2.3 + Latest From b621806a7c5a314264566597e1d4af3ea7f9b2fe Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 10 May 2018 16:27:27 +0200 Subject: [PATCH 032/138] Update build config --- .../MQTTnet.Netstandard/MQTTnet.NetStandard.csproj | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj b/Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj index cb154c24..fc1e41af 100644 --- a/Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj +++ b/Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj @@ -6,7 +6,6 @@ MQTTnet MQTTnet False - Full 0.0.0.0 0.0.0.0 0.0.0.0 @@ -19,7 +18,7 @@ false - + false UAP,Version=v10.0 UAP @@ -32,6 +31,10 @@ $(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets + + Full + + @@ -43,17 +46,17 @@ - + - + - + From e7a7bb1a7b349c0068e6837046885aa5ee1eca83 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Thu, 10 May 2018 19:18:31 +0200 Subject: [PATCH 033/138] added benchmark for serializers --- .../MqttApplicationMessageExtensions.cs | 2 +- .../Internal}/TestMqttChannel.cs | 2 +- .../MQTTnet.Benchmarks.csproj | 1 + Tests/MQTTnet.Benchmarks/Program.cs | 20 ++++- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 75 +++++++++++++++++++ .../MqttPacketReaderTests.cs | 1 + 6 files changed, 98 insertions(+), 3 deletions(-) rename {Tests/MQTTnet.Core.Tests => Frameworks/MQTTnet.NetStandard/Internal}/TestMqttChannel.cs (96%) create mode 100644 Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs diff --git a/Frameworks/MQTTnet.NetStandard/Internal/MqttApplicationMessageExtensions.cs b/Frameworks/MQTTnet.NetStandard/Internal/MqttApplicationMessageExtensions.cs index b1d1a389..79eb2c43 100644 --- a/Frameworks/MQTTnet.NetStandard/Internal/MqttApplicationMessageExtensions.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/MqttApplicationMessageExtensions.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Internal { - internal static class MqttApplicationMessageExtensions + public static class MqttApplicationMessageExtensions { public static MqttApplicationMessage ToApplicationMessage(this MqttPublishPacket publishPacket) { diff --git a/Tests/MQTTnet.Core.Tests/TestMqttChannel.cs b/Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs similarity index 96% rename from Tests/MQTTnet.Core.Tests/TestMqttChannel.cs rename to Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs index 2b4914b7..b380b085 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Channel; -namespace MQTTnet.Core.Tests +namespace MQTTnet.Core.Internal { public class TestMqttChannel : IMqttChannel { diff --git a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index 49773119..3b52bbae 100644 --- a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -150,6 +150,7 @@ + diff --git a/Tests/MQTTnet.Benchmarks/Program.cs b/Tests/MQTTnet.Benchmarks/Program.cs index 965ddb55..ad2c3630 100644 --- a/Tests/MQTTnet.Benchmarks/Program.cs +++ b/Tests/MQTTnet.Benchmarks/Program.cs @@ -1,5 +1,7 @@ using System; +using System.Threading; using BenchmarkDotNet.Running; +using MQTTnet.Diagnostics; namespace MQTTnet.Benchmarks { @@ -7,7 +9,23 @@ namespace MQTTnet.Benchmarks { public static void Main(string[] args) { - var summary = BenchmarkRunner.Run(); + Console.WriteLine($"MQTTnet - BenchmarkApp.{TargetFrameworkInfoProvider.TargetFramework}"); + Console.WriteLine("1 = MessageProcessingBenchmark"); + Console.WriteLine("2 = SerializerBenchmark"); + + var pressedKey = Console.ReadKey(true); + switch (pressedKey.KeyChar) + { + case '1': + BenchmarkRunner.Run(); + break; + case '2': + BenchmarkRunner.Run(); + break; + default: + break; + } + Console.ReadLine(); } } diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs new file mode 100644 index 00000000..d343eb14 --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -0,0 +1,75 @@ +using BenchmarkDotNet.Attributes; +using MQTTnet.Client; +using MQTTnet.Packets; +using MQTTnet.Serializer; +using MQTTnet.Internal; +using MQTTnet.Server; +using BenchmarkDotNet.Attributes.Jobs; +using BenchmarkDotNet.Attributes.Exporters; +using System; +using System.Threading; +using System.IO; +using MQTTnet.Core.Internal; + +namespace MQTTnet.Benchmarks +{ + [ClrJob] + [RPlotExporter] + [MemoryDiagnoser] + public class SerializerBenchmark + { + private MqttApplicationMessage _message; + private MqttBasePacket _packet; + private ArraySegment _serializedPacket; + private MqttPacketSerializer _serializer; + + [GlobalSetup] + public void Setup() + { + _message = new MqttApplicationMessageBuilder() + .WithTopic("A") + .Build(); + + _packet = _message.ToPublishPacket(); + _serializer = new MqttPacketSerializer(); + _serializedPacket = _serializer.Serialize(_packet); + } + + [Benchmark] + public void Serialize_10000_Messages() + { + for (var i = 0; i < 10000; i++) + { + _serializer.Serialize(_packet); + } + } + + [Benchmark] + public void Deserialize_10000_Messages() + { + for (var i = 0; i < 10000; i++) + { + using (var headerStream = new MemoryStream(Join(_serializedPacket))) + { + var header = MqttPacketReader.ReadHeaderAsync(new TestMqttChannel(headerStream), CancellationToken.None).GetAwaiter().GetResult(); + + using (var bodyStream = new MemoryStream(Join(_serializedPacket), (int)headerStream.Position, header.BodyLength)) + { + var deserializedPacket = _serializer.Deserialize(header, bodyStream); + } + } + } + } + + private static byte[] Join(params ArraySegment[] chunks) + { + var buffer = new MemoryStream(); + foreach (var chunk in chunks) + { + buffer.Write(chunk.Array, chunk.Offset, chunk.Count); + } + + return buffer.ToArray(); + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs index ca48ad9b..72a675bf 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs @@ -1,6 +1,7 @@ using System.IO; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Core.Internal; using MQTTnet.Serializer; namespace MQTTnet.Core.Tests From 17db90d5abfab790e36ef6c24d8b7e6d9e50ca53 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Thu, 10 May 2018 19:25:33 +0200 Subject: [PATCH 034/138] fixed build --- Tests/MQTTnet.Core.Tests/ExtensionTests.cs | 12 ++++++------ .../MQTTnet.Core.Tests/MqttPacketSerializerTests.cs | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs index daeea620..696cfa3c 100644 --- a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs +++ b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs @@ -14,20 +14,20 @@ namespace MQTTnet.Core.Tests [TestMethod] public async Task TimeoutAfter() { - await Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct), TimeSpan.FromMilliseconds(100), CancellationToken.None); + await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct), TimeSpan.FromMilliseconds(100), CancellationToken.None); } [ExpectedException(typeof(MqttCommunicationTimedOutException))] [TestMethod] public async Task TimeoutAfterWithResult() { - await Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(100), CancellationToken.None); + await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(100), CancellationToken.None); } [TestMethod] public async Task TimeoutAfterCompleteInTime() { - var result = await 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.TimeoutAfter(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 Internal.TaskExtensions.TimeoutAfter(ct => Task.Run(() => + await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Run(() => { var iis = new int[0]; iis[1] = 0; @@ -55,7 +55,7 @@ namespace MQTTnet.Core.Tests { try { - await Internal.TaskExtensions.TimeoutAfter(ct => Task.Run(() => + await MQTTnet.Internal.TaskExtensions.TimeoutAfter(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 Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct), TimeSpan.FromMinutes(1), CancellationToken.None); + return MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct), TimeSpan.FromMinutes(1), CancellationToken.None); }); await Task.WhenAll(tasks); diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 580bc4d4..a45736d2 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Text; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Core.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; using MQTTnet.Serializer; From 97d91ec0f87cc52f0e523e7d2a857bdf2b8f62ab Mon Sep 17 00:00:00 2001 From: JanEggers Date: Thu, 10 May 2018 19:30:29 +0200 Subject: [PATCH 035/138] fixed code quality issues --- Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index d343eb14..bcfff1c3 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -18,7 +18,6 @@ namespace MQTTnet.Benchmarks [MemoryDiagnoser] public class SerializerBenchmark { - private MqttApplicationMessage _message; private MqttBasePacket _packet; private ArraySegment _serializedPacket; private MqttPacketSerializer _serializer; @@ -26,11 +25,11 @@ namespace MQTTnet.Benchmarks [GlobalSetup] public void Setup() { - _message = new MqttApplicationMessageBuilder() + var message = new MqttApplicationMessageBuilder() .WithTopic("A") .Build(); - _packet = _message.ToPublishPacket(); + _packet = message.ToPublishPacket(); _serializer = new MqttPacketSerializer(); _serializedPacket = _serializer.Serialize(_packet); } @@ -55,7 +54,7 @@ namespace MQTTnet.Benchmarks using (var bodyStream = new MemoryStream(Join(_serializedPacket), (int)headerStream.Position, header.BodyLength)) { - var deserializedPacket = _serializer.Deserialize(header, bodyStream); + _serializer.Deserialize(header, bodyStream); } } } From c569c1074002f451fa6a782628e0f9c932ebcfb7 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Thu, 10 May 2018 20:50:50 +0200 Subject: [PATCH 036/138] dont use writer instance --- .../Serializer/MqttPacketReader.cs | 37 ++- .../Serializer/MqttPacketSerializer.cs | 231 +++++++++--------- .../Serializer/MqttPacketWriter.cs | 36 +-- 3 files changed, 139 insertions(+), 165 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs index 4c435790..41239246 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs @@ -10,18 +10,8 @@ using MQTTnet.Protocol; namespace MQTTnet.Serializer { - public sealed class MqttPacketReader : BinaryReader + public static class MqttPacketReader { - private readonly MqttPacketHeader _header; - - public MqttPacketReader(MqttPacketHeader header, Stream bodyStream) - : base(bodyStream, Encoding.UTF8, true) - { - _header = header; - } - - public bool EndOfRemainingData => BaseStream.Position == _header.BodyLength; - public static async Task ReadHeaderAsync(IMqttChannel stream, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -51,9 +41,9 @@ namespace MQTTnet.Serializer }; } - public override ushort ReadUInt16() + public static ushort ReadUInt16(this Stream stream) { - var buffer = ReadBytes(2); + var buffer = stream.ReadBytes(2); var temp = buffer[0]; buffer[0] = buffer[1]; @@ -62,9 +52,9 @@ namespace MQTTnet.Serializer return BitConverter.ToUInt16(buffer, 0); } - public string ReadStringWithLengthPrefix() + public static string ReadStringWithLengthPrefix(this Stream stream) { - var buffer = ReadWithLengthPrefix(); + var buffer = stream.ReadWithLengthPrefix(); if (buffer.Length == 0) { return string.Empty; @@ -73,20 +63,27 @@ namespace MQTTnet.Serializer return Encoding.UTF8.GetString(buffer, 0, buffer.Length); } - public byte[] ReadWithLengthPrefix() + public static byte[] ReadWithLengthPrefix(this Stream stream) { - var length = ReadUInt16(); + var length = stream.ReadUInt16(); if (length == 0) { return new byte[0]; } - return ReadBytes(length); + return stream.ReadBytes(length); } - public byte[] ReadRemainingData() + public static byte[] ReadRemainingData(this Stream stream, MqttPacketHeader header) { - return ReadBytes(_header.BodyLength - (int)BaseStream.Position); + return stream.ReadBytes(header.BodyLength - (int)stream.Position); + } + + public static byte[] ReadBytes(this Stream stream, int count) + { + var buffer = new byte[count]; + stream.Read(buffer, 0, count); + return buffer; } private static async Task ReadBodyLengthAsync(IMqttChannel stream, CancellationToken cancellationToken) diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 18818ce8..a4e05a02 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -20,11 +20,10 @@ namespace MQTTnet.Serializer if (packet == null) throw new ArgumentNullException(nameof(packet)); using (var stream = new MemoryStream(128)) - using (var writer = new MqttPacketWriter(stream)) { // Leave enough head space for max header size (fixed + 4 variable remaining length) stream.Position = 5; - var fixedHeader = SerializePacket(packet, writer); + var fixedHeader = SerializePacket(packet, stream); stream.Position = 1; var remainingLength = MqttPacketWriter.EncodeRemainingLength((int)stream.Length - 5, stream); @@ -35,7 +34,7 @@ namespace MQTTnet.Serializer // Position cursor on correct offset on beginining of array (has leading 0x0) stream.Position = headerOffset; - writer.Write(fixedHeader); + stream.WriteByte(fixedHeader); #if NET461 || NET452 || NETSTANDARD2_0 var buffer = stream.GetBuffer(); @@ -46,146 +45,138 @@ namespace MQTTnet.Serializer } } - public MqttBasePacket Deserialize(MqttPacketHeader header, Stream body) - { - if (header == null) throw new ArgumentNullException(nameof(header)); - if (body == null) throw new ArgumentNullException(nameof(body)); - - using (var reader = new MqttPacketReader(header, body)) - { - return Deserialize(header, reader); - } - } - - private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter writer) + private byte SerializePacket(MqttBasePacket packet, Stream stream) { switch (packet) { - case MqttConnectPacket connectPacket: return Serialize(connectPacket, writer); - case MqttConnAckPacket connAckPacket: return Serialize(connAckPacket, writer); + 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, writer); - case MqttPubAckPacket pubAckPacket: return Serialize(pubAckPacket, writer); - case MqttPubRecPacket pubRecPacket: return Serialize(pubRecPacket, writer); - case MqttPubRelPacket pubRelPacket: return Serialize(pubRelPacket, writer); - case MqttPubCompPacket pubCompPacket: return Serialize(pubCompPacket, writer); - case MqttSubscribePacket subscribePacket: return Serialize(subscribePacket, writer); - case MqttSubAckPacket subAckPacket: return Serialize(subAckPacket, writer); - case MqttUnsubscribePacket unsubscribePacket: return Serialize(unsubscribePacket, writer); - case MqttUnsubAckPacket unsubAckPacket: return Serialize(unsubAckPacket, writer); + 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."); } } - private MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader) + public MqttBasePacket Deserialize(MqttPacketHeader header, Stream stream) { + if (header == null) throw new ArgumentNullException(nameof(header)); + if (stream == null) throw new ArgumentNullException(nameof(stream)); + switch (header.ControlPacketType) { - case MqttControlPacketType.Connect: return DeserializeConnect(reader); - case MqttControlPacketType.ConnAck: return DeserializeConnAck(reader); + case MqttControlPacketType.Connect: return DeserializeConnect(stream); + case MqttControlPacketType.ConnAck: return DeserializeConnAck(stream); case MqttControlPacketType.Disconnect: return new MqttDisconnectPacket(); - case MqttControlPacketType.Publish: return DeserializePublish(reader, header); - case MqttControlPacketType.PubAck: return DeserializePubAck(reader); - case MqttControlPacketType.PubRec: return DeserializePubRec(reader); - case MqttControlPacketType.PubRel: return DeserializePubRel(reader); - case MqttControlPacketType.PubComp: return DeserializePubComp(reader); + case MqttControlPacketType.Publish: return DeserializePublish(stream, header); + case MqttControlPacketType.PubAck: return DeserializePubAck(stream); + case MqttControlPacketType.PubRec: return DeserializePubRec(stream); + case MqttControlPacketType.PubRel: return DeserializePubRel(stream); + case MqttControlPacketType.PubComp: return DeserializePubComp(stream); case MqttControlPacketType.PingReq: return new MqttPingReqPacket(); case MqttControlPacketType.PingResp: return new MqttPingRespPacket(); - case MqttControlPacketType.Subscribe: return DeserializeSubscribe(reader); - case MqttControlPacketType.SubAck: return DeserializeSubAck(reader); - case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(reader); - case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(reader); + case MqttControlPacketType.Subscribe: return DeserializeSubscribe(stream, header); + case MqttControlPacketType.SubAck: return DeserializeSubAck(stream, header); + case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(stream, header); + case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(stream); default: throw new MqttProtocolViolationException($"Packet type ({(int)header.ControlPacketType}) not supported."); } } - private static MqttBasePacket DeserializeUnsubAck(MqttPacketReader reader) + private static MqttBasePacket DeserializeUnsubAck(Stream stream) { return new MqttUnsubAckPacket { - PacketIdentifier = reader.ReadUInt16() + PacketIdentifier = stream.ReadUInt16() }; } - private static MqttBasePacket DeserializePubComp(MqttPacketReader reader) + private static MqttBasePacket DeserializePubComp(Stream stream) { return new MqttPubCompPacket { - PacketIdentifier = reader.ReadUInt16() + PacketIdentifier = stream.ReadUInt16() }; } - private static MqttBasePacket DeserializePubRel(MqttPacketReader reader) + private static MqttBasePacket DeserializePubRel(Stream stream) { return new MqttPubRelPacket { - PacketIdentifier = reader.ReadUInt16() + PacketIdentifier = stream.ReadUInt16() }; } - private static MqttBasePacket DeserializePubRec(MqttPacketReader reader) + private static MqttBasePacket DeserializePubRec(Stream stream) { return new MqttPubRecPacket { - PacketIdentifier = reader.ReadUInt16() + PacketIdentifier = stream.ReadUInt16() }; } - private static MqttBasePacket DeserializePubAck(MqttPacketReader reader) + private static MqttBasePacket DeserializePubAck(Stream stream) { return new MqttPubAckPacket { - PacketIdentifier = reader.ReadUInt16() + PacketIdentifier = stream.ReadUInt16() }; } - private static MqttBasePacket DeserializeUnsubscribe(MqttPacketReader reader) + private static MqttBasePacket DeserializeUnsubscribe(Stream stream, MqttPacketHeader header) { var packet = new MqttUnsubscribePacket { - PacketIdentifier = reader.ReadUInt16(), + PacketIdentifier = stream.ReadUInt16(), }; - while (!reader.EndOfRemainingData) + while (stream.Position != header.BodyLength) { - packet.TopicFilters.Add(reader.ReadStringWithLengthPrefix()); + packet.TopicFilters.Add(stream.ReadStringWithLengthPrefix()); } return packet; } - private static MqttBasePacket DeserializeSubscribe(MqttPacketReader reader) + private static MqttBasePacket DeserializeSubscribe(Stream stream, MqttPacketHeader header) { var packet = new MqttSubscribePacket { - PacketIdentifier = reader.ReadUInt16() + PacketIdentifier = stream.ReadUInt16() }; - while (!reader.EndOfRemainingData) + while (stream.Position != header.BodyLength) { packet.TopicFilters.Add(new TopicFilter( - reader.ReadStringWithLengthPrefix(), - (MqttQualityOfServiceLevel)reader.ReadByte())); + stream.ReadStringWithLengthPrefix(), + (MqttQualityOfServiceLevel)stream.ReadByte())); } return packet; } - private static MqttBasePacket DeserializePublish(MqttPacketReader reader, MqttPacketHeader mqttPacketHeader) + private static MqttBasePacket DeserializePublish(Stream stream, MqttPacketHeader mqttPacketHeader) { var fixedHeader = new ByteReader(mqttPacketHeader.FixedHeader); var retain = fixedHeader.Read(); var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2); var dup = fixedHeader.Read(); - var topic = reader.ReadStringWithLengthPrefix(); + var topic = stream.ReadStringWithLengthPrefix(); ushort? packetIdentifier = null; if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { - packetIdentifier = reader.ReadUInt16(); + packetIdentifier = stream.ReadUInt16(); } var packet = new MqttPublishPacket @@ -193,7 +184,7 @@ namespace MQTTnet.Serializer PacketIdentifier = packetIdentifier, Retain = retain, Topic = topic, - Payload = reader.ReadRemainingData(), + Payload = stream.ReadRemainingData(mqttPacketHeader), QualityOfServiceLevel = qualityOfServiceLevel, Dup = dup }; @@ -201,12 +192,12 @@ namespace MQTTnet.Serializer return packet; } - private static MqttBasePacket DeserializeConnect(MqttPacketReader reader) + private static MqttBasePacket DeserializeConnect(Stream stream) { - reader.ReadBytes(2); // Skip 2 bytes for header and remaining length. + stream.ReadBytes(2); // Skip 2 bytes for header and remaining length. MqttProtocolVersion protocolVersion; - var protocolName = reader.ReadBytes(4); + var protocolName = stream.ReadBytes(4); if (protocolName.SequenceEqual(ProtocolVersionV311Name)) { @@ -216,7 +207,7 @@ namespace MQTTnet.Serializer { var buffer = new byte[6]; Array.Copy(protocolName, buffer, 4); - protocolName = reader.ReadBytes(2); + protocolName = stream.ReadBytes(2); Array.Copy(protocolName, 0, buffer, 4, 2); if (protocolName.SequenceEqual(ProtocolVersionV310Name)) @@ -229,8 +220,8 @@ namespace MQTTnet.Serializer } } - reader.ReadByte(); // Skip protocol level - var connectFlags = reader.ReadByte(); + stream.ReadByte(); // Skip protocol level + var connectFlags = stream.ReadByte(); var connectFlagsReader = new ByteReader(connectFlags); connectFlagsReader.Read(); // Reserved. @@ -247,15 +238,15 @@ namespace MQTTnet.Serializer var passwordFlag = connectFlagsReader.Read(); var usernameFlag = connectFlagsReader.Read(); - packet.KeepAlivePeriod = reader.ReadUInt16(); - packet.ClientId = reader.ReadStringWithLengthPrefix(); + packet.KeepAlivePeriod = stream.ReadUInt16(); + packet.ClientId = stream.ReadStringWithLengthPrefix(); if (willFlag) { packet.WillMessage = new MqttApplicationMessage { - Topic = reader.ReadStringWithLengthPrefix(), - Payload = reader.ReadWithLengthPrefix(), + Topic = stream.ReadStringWithLengthPrefix(), + Payload = stream.ReadWithLengthPrefix(), QualityOfServiceLevel = (MqttQualityOfServiceLevel)willQoS, Retain = willRetain }; @@ -263,45 +254,45 @@ namespace MQTTnet.Serializer if (usernameFlag) { - packet.Username = reader.ReadStringWithLengthPrefix(); + packet.Username = stream.ReadStringWithLengthPrefix(); } if (passwordFlag) { - packet.Password = reader.ReadStringWithLengthPrefix(); + packet.Password = stream.ReadStringWithLengthPrefix(); } ValidateConnectPacket(packet); return packet; } - private static MqttBasePacket DeserializeSubAck(MqttPacketReader reader) + private static MqttBasePacket DeserializeSubAck(Stream stream, MqttPacketHeader header) { var packet = new MqttSubAckPacket { - PacketIdentifier = reader.ReadUInt16() + PacketIdentifier = stream.ReadUInt16() }; - while (!reader.EndOfRemainingData) + while (stream.Position != header.BodyLength) { - packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)reader.ReadByte()); + packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)stream.ReadByte()); } return packet; } - private MqttBasePacket DeserializeConnAck(MqttPacketReader reader) + private MqttBasePacket DeserializeConnAck(Stream stream) { var packet = new MqttConnAckPacket(); - var firstByteReader = new ByteReader(reader.ReadByte()); + var firstByteReader = new ByteReader(stream.ReadByte()); if (ProtocolVersion == MqttProtocolVersion.V311) { packet.IsSessionPresent = firstByteReader.Read(); } - packet.ConnectReturnCode = (MqttConnectReturnCode)reader.ReadByte(); + packet.ConnectReturnCode = (MqttConnectReturnCode)stream.ReadByte(); return packet; } @@ -326,20 +317,20 @@ namespace MQTTnet.Serializer } } - private byte Serialize(MqttConnectPacket packet, MqttPacketWriter writer) + private byte Serialize(MqttConnectPacket packet, Stream stream) { ValidateConnectPacket(packet); // Write variable header if (ProtocolVersion == MqttProtocolVersion.V311) { - writer.WriteWithLengthPrefix(ProtocolVersionV311Name); - writer.Write(0x04); // 3.1.2.2 Protocol Level 4 + stream.WriteWithLengthPrefix(ProtocolVersionV311Name); + stream.WriteByte(0x04); // 3.1.2.2 Protocol Level 4 } else { - writer.WriteWithLengthPrefix(ProtocolVersionV310Name); - writer.Write(0x03); // Protocol Level 3 + stream.WriteWithLengthPrefix(ProtocolVersionV310Name); + stream.WriteByte(0x03); // Protocol Level 3 } var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags @@ -366,68 +357,68 @@ namespace MQTTnet.Serializer connectFlags.Write(packet.Password != null); connectFlags.Write(packet.Username != null); - writer.Write(connectFlags); - writer.Write(packet.KeepAlivePeriod); - writer.WriteWithLengthPrefix(packet.ClientId); + stream.Write(connectFlags); + stream.Write(packet.KeepAlivePeriod); + stream.WriteWithLengthPrefix(packet.ClientId); if (packet.WillMessage != null) { - writer.WriteWithLengthPrefix(packet.WillMessage.Topic); - writer.WriteWithLengthPrefix(packet.WillMessage.Payload); + stream.WriteWithLengthPrefix(packet.WillMessage.Topic); + stream.WriteWithLengthPrefix(packet.WillMessage.Payload); } if (packet.Username != null) { - writer.WriteWithLengthPrefix(packet.Username); + stream.WriteWithLengthPrefix(packet.Username); } if (packet.Password != null) { - writer.WriteWithLengthPrefix(packet.Password); + stream.WriteWithLengthPrefix(packet.Password); } return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect); } - private byte Serialize(MqttConnAckPacket packet, MqttPacketWriter writer) + private byte Serialize(MqttConnAckPacket packet, Stream stream) { if (ProtocolVersion == MqttProtocolVersion.V310) { - writer.Write(0); + stream.WriteByte(0); } else if (ProtocolVersion == MqttProtocolVersion.V311) { var connectAcknowledgeFlags = new ByteWriter(); connectAcknowledgeFlags.Write(packet.IsSessionPresent); - writer.Write(connectAcknowledgeFlags); + stream.Write(connectAcknowledgeFlags); } else { throw new MqttProtocolViolationException("Protocol version not supported."); } - writer.Write((byte)packet.ConnectReturnCode); + stream.Write((byte)packet.ConnectReturnCode); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); } - private static byte Serialize(MqttPubRelPacket packet, MqttPacketWriter writer) + private static byte Serialize(MqttPubRelPacket packet, Stream stream) { if (!packet.PacketIdentifier.HasValue) { throw new MqttProtocolViolationException("PubRel packet has no packet identifier."); } - writer.Write(packet.PacketIdentifier.Value); + stream.Write(packet.PacketIdentifier.Value); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02); } - private static byte Serialize(MqttPublishPacket packet, MqttPacketWriter writer) + private static byte Serialize(MqttPublishPacket packet, Stream stream) { ValidatePublishPacket(packet); - writer.WriteWithLengthPrefix(packet.Topic); + stream.WriteWithLengthPrefix(packet.Topic); if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { @@ -436,7 +427,7 @@ namespace MQTTnet.Serializer throw new MqttProtocolViolationException("Publish packet has no packet identifier."); } - writer.Write(packet.PacketIdentifier.Value); + stream.Write(packet.PacketIdentifier.Value); } else { @@ -448,7 +439,7 @@ namespace MQTTnet.Serializer if (packet.Payload?.Length > 0) { - writer.Write(packet.Payload); + stream.Write(packet.Payload, 0, packet.Payload.Length); } byte fixedHeader = 0; @@ -468,43 +459,43 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader); } - private static byte Serialize(MqttPubAckPacket packet, MqttPacketWriter writer) + private static byte Serialize(MqttPubAckPacket packet, Stream stream) { if (!packet.PacketIdentifier.HasValue) { throw new MqttProtocolViolationException("PubAck packet has no packet identifier."); } - writer.Write(packet.PacketIdentifier.Value); + stream.Write(packet.PacketIdentifier.Value); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck); } - private static byte Serialize(MqttPubRecPacket packet, MqttPacketWriter writer) + private static byte Serialize(MqttPubRecPacket packet, Stream stream) { if (!packet.PacketIdentifier.HasValue) { throw new MqttProtocolViolationException("PubRec packet has no packet identifier."); } - writer.Write(packet.PacketIdentifier.Value); + stream.Write(packet.PacketIdentifier.Value); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec); } - private static byte Serialize(MqttPubCompPacket packet, MqttPacketWriter writer) + private static byte Serialize(MqttPubCompPacket packet, Stream stream) { if (!packet.PacketIdentifier.HasValue) { throw new MqttProtocolViolationException("PubComp packet has no packet identifier."); } - writer.Write(packet.PacketIdentifier.Value); + stream.Write(packet.PacketIdentifier.Value); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp); } - private static byte Serialize(MqttSubscribePacket packet, MqttPacketWriter writer) + private static byte Serialize(MqttSubscribePacket packet, Stream stream) { if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3]."); @@ -513,41 +504,41 @@ namespace MQTTnet.Serializer throw new MqttProtocolViolationException("Subscribe packet has no packet identifier."); } - writer.Write(packet.PacketIdentifier.Value); + stream.Write(packet.PacketIdentifier.Value); if (packet.TopicFilters?.Count > 0) { foreach (var topicFilter in packet.TopicFilters) { - writer.WriteWithLengthPrefix(topicFilter.Topic); - writer.Write((byte)topicFilter.QualityOfServiceLevel); + stream.WriteWithLengthPrefix(topicFilter.Topic); + stream.WriteByte((byte)topicFilter.QualityOfServiceLevel); } } return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02); } - private static byte Serialize(MqttSubAckPacket packet, MqttPacketWriter writer) + private static byte Serialize(MqttSubAckPacket packet, Stream stream) { if (!packet.PacketIdentifier.HasValue) { throw new MqttProtocolViolationException("SubAck packet has no packet identifier."); } - writer.Write(packet.PacketIdentifier.Value); + stream.Write(packet.PacketIdentifier.Value); if (packet.SubscribeReturnCodes?.Any() == true) { foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes) { - writer.Write((byte)packetSubscribeReturnCode); + stream.WriteByte((byte)packetSubscribeReturnCode); } } return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.SubAck); } - private static byte Serialize(MqttUnsubscribePacket packet, MqttPacketWriter writer) + private static byte Serialize(MqttUnsubscribePacket packet, Stream stream) { if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2]."); @@ -556,27 +547,27 @@ namespace MQTTnet.Serializer throw new MqttProtocolViolationException("Unsubscribe packet has no packet identifier."); } - writer.Write(packet.PacketIdentifier.Value); + stream.Write(packet.PacketIdentifier.Value); if (packet.TopicFilters?.Any() == true) { foreach (var topicFilter in packet.TopicFilters) { - writer.WriteWithLengthPrefix(topicFilter); + stream.WriteWithLengthPrefix(topicFilter); } } return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02); } - private static byte Serialize(MqttUnsubAckPacket packet, BinaryWriter writer) + private static byte Serialize(MqttUnsubAckPacket packet, Stream stream) { if (!packet.PacketIdentifier.HasValue) { throw new MqttProtocolViolationException("UnsubAck packet has no packet identifier."); } - writer.Write(packet.PacketIdentifier.Value); + stream.Write(packet.PacketIdentifier.Value); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck); } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs index 54f40eb2..7d75e7ad 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs @@ -5,13 +5,8 @@ using MQTTnet.Protocol; namespace MQTTnet.Serializer { - public sealed class MqttPacketWriter : BinaryWriter + public static class MqttPacketWriter { - public MqttPacketWriter(Stream stream) - : base(stream, Encoding.UTF8, true) - { - } - public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0) { var fixedHeader = (int)packetType << 4; @@ -19,40 +14,31 @@ namespace MQTTnet.Serializer return (byte)fixedHeader; } - public override void Write(ushort value) + public static void Write(this Stream stream, ushort value) { var buffer = BitConverter.GetBytes(value); - Write(buffer[1], buffer[0]); + stream.WriteByte(buffer[1]); + stream.WriteByte(buffer[0]); } - public new void Write(params byte[] values) - { - base.Write(values); - } - - public new void Write(byte value) - { - base.Write(value); - } - - public void Write(ByteWriter value) + public static void Write(this Stream stream, ByteWriter value) { if (value == null) throw new ArgumentNullException(nameof(value)); - Write(value.Value); + stream.Write(value.Value); } - public void WriteWithLengthPrefix(string value) + public static void WriteWithLengthPrefix(this Stream stream, string value) { - WriteWithLengthPrefix(Encoding.UTF8.GetBytes(value ?? string.Empty)); + stream.WriteWithLengthPrefix(Encoding.UTF8.GetBytes(value ?? string.Empty)); } - public void WriteWithLengthPrefix(byte[] value) + public static void WriteWithLengthPrefix(this Stream stream, byte[] value) { var length = (ushort)value.Length; - Write(length); - Write(value); + stream.Write(length); + stream.Write(value, 0, length); } public static int EncodeRemainingLength(int length, MemoryStream stream) From 8c37cf16c03c5f7cb8f5e069702cd76bff7c82b0 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Thu, 10 May 2018 22:28:01 +0200 Subject: [PATCH 037/138] fixed tests --- .../MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs | 2 +- Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index a4e05a02..12330b5d 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -397,7 +397,7 @@ namespace MQTTnet.Serializer throw new MqttProtocolViolationException("Protocol version not supported."); } - stream.Write((byte)packet.ConnectReturnCode); + stream.WriteByte((byte)packet.ConnectReturnCode); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs index 7d75e7ad..1d040183 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs @@ -25,7 +25,7 @@ namespace MQTTnet.Serializer { if (value == null) throw new ArgumentNullException(nameof(value)); - stream.Write(value.Value); + stream.WriteByte(value.Value); } public static void WriteWithLengthPrefix(this Stream stream, string value) From 7b987635f77f1856df70563d8b4aea4fa8ae5dab Mon Sep 17 00:00:00 2001 From: VladimirAkopyan Date: Fri, 11 May 2018 16:39:59 +0100 Subject: [PATCH 038/138] ref #230 Limiting the growth of PendingMessagesQueue to a configurable value. it will only store X lated messages, 250 by default. https://github.com/chkr1011/MQTTnet/issues/230 --- .../Server/IMqttServerOptions.cs | 2 ++ .../Server/MqttClientPendingMessagesQueue.cs | 11 +++++++++++ .../Server/MqttClientSession.cs | 5 ++++- .../Server/MqttServerOptions.cs | 2 ++ .../Server/MqttServerOptionsBuilder.cs | 6 ++++++ MQTTnet.sln | 18 ++++++++++++++++++ 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs index cc58eeb8..9ae7715f 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs @@ -5,6 +5,8 @@ namespace MQTTnet.Server public interface IMqttServerOptions { int ConnectionBacklog { get; } + int MaxPendingMessagesPerClient { get; } + TimeSpan DefaultCommunicationTimeout { get; } Action ConnectionValidator { get; } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs index 99493874..f646a196 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs @@ -50,6 +50,17 @@ namespace MQTTnet.Server } } + public async Task DropPacket() + { + MqttBasePacket packet = null; + await _queueWaitSemaphore.WaitAsync().ConfigureAwait(false); + if (!_queue.TryDequeue(out packet)) + { + throw new InvalidOperationException(); // should not happen + } + _queueWaitSemaphore.Release(); + } + public void Enqueue(MqttBasePacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs index 8514da99..fbb11b9a 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs @@ -142,7 +142,10 @@ namespace MQTTnet.Server { publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); } - + if (_options.MaxPendingMessagesPerClient <= PendingMessagesQueue.Count) + { + await PendingMessagesQueue.DropPacket(); + } PendingMessagesQueue.Enqueue(publishPacket); } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs index 2b0a9a00..4315fcfb 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs @@ -9,6 +9,8 @@ namespace MQTTnet.Server public MqttServerTlsEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsEndpointOptions(); public int ConnectionBacklog { get; set; } = 10; + + public int MaxPendingMessagesPerClient { get; set; } = 250; public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15); diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs index 18b2de29..2c865125 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs @@ -13,6 +13,12 @@ namespace MQTTnet.Server return this; } + public MqttServerOptionsBuilder WithMaxPendingMessagesPerClient(int value) + { + _options.MaxPendingMessagesPerClient = value; + return this; + } + public MqttServerOptionsBuilder WithDefaultCommunicationTimeout(TimeSpan value) { _options.DefaultCommunicationTimeout = value; diff --git a/MQTTnet.sln b/MQTTnet.sln index fab76088..2b316731 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -38,6 +38,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTserver", "MQTTserver\MQTTserver.csproj", "{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -168,6 +170,22 @@ Global {C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x64.Build.0 = Release|Any CPU {C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x86.ActiveCfg = Release|Any CPU {C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x86.Build.0 = Release|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|ARM.ActiveCfg = Debug|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|ARM.Build.0 = Debug|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|x64.ActiveCfg = Debug|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|x64.Build.0 = Debug|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|x86.ActiveCfg = Debug|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|x86.Build.0 = Debug|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|Any CPU.Build.0 = Release|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|ARM.ActiveCfg = Release|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|ARM.Build.0 = Release|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|x64.ActiveCfg = Release|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|x64.Build.0 = Release|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|x86.ActiveCfg = Release|Any CPU + {5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From e51042ed0422c92734b638fa99a63ffd97d23cc0 Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 11 May 2018 20:05:59 +0200 Subject: [PATCH 039/138] Update UWP version --- Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj | 4 ++-- .../MQTTnet.TestApp.UniversalWindows.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj index fc1e41af..83643782 100644 --- a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj +++ b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj @@ -22,7 +22,7 @@ false UAP,Version=v10.0 UAP - 10.0.16299.0 + 10.0.17134.0 10.0.10240.0 .NETCore v5.0 @@ -56,7 +56,7 @@ - + diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj index b5d2dbd9..73636f04 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj @@ -11,7 +11,7 @@ MQTTnet.TestApp.UniversalWindows en-US UAP - 10.0.16299.0 + 10.0.17134.0 10.0.16299.0 14 512 From febc8f774d0abb1edd59d4f23c1e34161f51db3a Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 11 May 2018 20:06:10 +0200 Subject: [PATCH 040/138] Start a dedicated thread per client. --- Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs | 6 ++++-- Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs index b32869d9..03241344 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs @@ -156,9 +156,11 @@ namespace MQTTnet.Server private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) { - eventArgs.SessionTask = Task.Run( + eventArgs.SessionTask = Task.Factory.StartNew( () => _clientSessionsManager.RunSessionAsync(eventArgs.Client, _cancellationTokenSource.Token), - _cancellationTokenSource.Token); + _cancellationTokenSource.Token, + TaskCreationOptions.LongRunning, + TaskScheduler.Current); } } } diff --git a/Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj b/Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj index fc1e41af..83643782 100644 --- a/Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj +++ b/Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj @@ -22,7 +22,7 @@ false UAP,Version=v10.0 UAP - 10.0.16299.0 + 10.0.17134.0 10.0.10240.0 .NETCore v5.0 @@ -56,7 +56,7 @@ - + From e14ea9a240b695452512e1cf2ab5791a7640365c Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 11 May 2018 20:49:31 +0200 Subject: [PATCH 041/138] Add overflow strategy for pending messages queue. --- Build/MQTTnet.nuspec | 4 +- .../MQTTnet.NetStandard/Client/MqttClient.cs | 51 +++++++++---------- .../Diagnostics/IMqttNetLogger.cs | 8 ++- .../Diagnostics/MqttNetLogger.cs | 36 +++++++++---- .../Server/IMqttServerOptions.cs | 1 + .../Server/MqttClientKeepAliveMonitor.cs | 6 +-- .../Server/MqttClientPendingMessagesQueue.cs | 49 ++++++++++-------- .../Server/MqttClientSession.cs | 7 +-- .../MqttPendingMessagesOverflowStrategy.cs | 8 +++ .../Server/MqttServerOptions.cs | 4 +- 10 files changed, 103 insertions(+), 71 deletions(-) create mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttPendingMessagesOverflowStrategy.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 2be9d25d..5f965a3e 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -10,9 +10,11 @@ https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png false MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). - * [Client] Received messages are now processed in the worker thread by default. Added a new setting for switching back to dedicated threads. + * [Core] Performance optimizations. +* [Client] Received messages are now processed in the worker thread by default. Added a new setting for switching back to dedicated threads. * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot). * [Server] The takeover of an existing client sessions is now treated as a _clean_ disconnect of the previous client. +* [Server] The pending messages queue per client is now limited to 250 messages. Overflow strategy and count can be changed via options (thanks to @VladimirAkopyan) Copyright Christian Kratky 2016-2018 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 diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index edf9b440..4f28a90b 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -57,14 +57,14 @@ namespace MQTTnet.Client _adapter = _adapterFactory.CreateClientAdapter(options, _logger); - _logger.Verbose("Trying to connect with server."); + _logger.Verbose(this, "Trying to connect with server."); await _adapter.ConnectAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false); - _logger.Verbose("Connection with server established."); + _logger.Verbose(this, "Connection with server established."); StartReceivingPackets(_cancellationTokenSource.Token); var connectResponse = await AuthenticateAsync(options.WillMessage, _cancellationTokenSource.Token).ConfigureAwait(false); - _logger.Verbose("MQTT connection with server established."); + _logger.Verbose(this, "MQTT connection with server established."); _sendTracker.Restart(); @@ -76,12 +76,12 @@ namespace MQTTnet.Client IsConnected = true; Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent)); - _logger.Info("Connected."); + _logger.Info(this, "Connected."); return new MqttClientConnectResult(connectResponse.IsSessionPresent); } catch (Exception exception) { - _logger.Error(exception, "Error while connecting with server."); + _logger.Error(this, exception, "Error while connecting with server."); await DisconnectInternalAsync(null, exception).ConfigureAwait(false); throw; @@ -233,7 +233,7 @@ namespace MQTTnet.Client private async Task DisconnectInternalAsync(Task sender, Exception exception) { - await _disconnectLock.WaitAsync(); + await _disconnectLock.WaitAsync().ConfigureAwait(false); try { if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested) @@ -245,7 +245,7 @@ namespace MQTTnet.Client } catch (Exception adapterException) { - _logger.Warning(adapterException, "Error while disconnecting from adapter."); + _logger.Warning(this, adapterException, "Error while disconnecting from adapter."); } finally { @@ -260,21 +260,16 @@ namespace MQTTnet.Client await WaitForTaskAsync(_packetReceiverTask, sender).ConfigureAwait(false); await WaitForTaskAsync(_keepAliveMessageSenderTask, sender).ConfigureAwait(false); - if (_keepAliveMessageSenderTask != null && _keepAliveMessageSenderTask != sender) - { - await _keepAliveMessageSenderTask.ConfigureAwait(false); - } - if (_adapter != null) { await _adapter.DisconnectAsync(_options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false); } - _logger.Verbose("Disconnected from adapter."); + _logger.Verbose(this, "Disconnected from adapter."); } catch (Exception adapterException) { - _logger.Warning(adapterException, "Error while disconnecting from adapter."); + _logger.Warning(this, adapterException, "Error while disconnecting from adapter."); } finally { @@ -283,7 +278,7 @@ namespace MQTTnet.Client _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; - _logger.Info("Disconnected."); + _logger.Info(this, "Disconnected."); Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception)); } } @@ -329,7 +324,7 @@ namespace MQTTnet.Client } catch (MqttCommunicationTimedOutException) { - _logger.Warning($"Timeout while waiting for packet of type '{typeof(TResponsePacket).Namespace}'."); + _logger.Warning(this, null, "Timeout while waiting for packet of type '{0}'.", typeof(TResponsePacket).Namespace); throw; } finally @@ -340,7 +335,7 @@ namespace MQTTnet.Client private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken) { - _logger.Verbose("Start sending keep alive packets."); + _logger.Verbose(this, "Start sending keep alive packets."); try { @@ -367,24 +362,24 @@ namespace MQTTnet.Client } else if (exception is MqttCommunicationException) { - _logger.Warning(exception, "MQTT communication exception while sending/receiving keep alive packets."); + _logger.Warning(this, exception, "MQTT communication exception while sending/receiving keep alive packets."); } else { - _logger.Error(exception, "Unhandled exception while sending/receiving keep alive packets."); + _logger.Error(this, exception, "Unhandled exception while sending/receiving keep alive packets."); } await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false); } finally { - _logger.Verbose("Stopped sending keep alive packets."); + _logger.Verbose(this, "Stopped sending keep alive packets."); } } private async Task ReceivePacketsAsync(CancellationToken cancellationToken) { - _logger.Verbose("Start receiving packets."); + _logger.Verbose(this, "Start receiving packets."); try { @@ -419,11 +414,11 @@ namespace MQTTnet.Client } else if (exception is MqttCommunicationException) { - _logger.Warning(exception, "MQTT communication exception while receiving packets."); + _logger.Warning(this, exception, "MQTT communication exception while receiving packets."); } else { - _logger.Error(exception, "Unhandled exception while receiving packets."); + _logger.Error(this, exception, "Unhandled exception while receiving packets."); } await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false); @@ -431,7 +426,7 @@ namespace MQTTnet.Client } finally { - _logger.Verbose("Stopped receiving packets."); + _logger.Verbose(this, "Stopped receiving packets."); } } @@ -467,7 +462,7 @@ namespace MQTTnet.Client } catch (Exception exception) { - _logger.Error(exception, "Unhandled exception while processing received packet."); + _logger.Error(this, exception, "Unhandled exception while processing received packet."); } } @@ -508,7 +503,7 @@ namespace MQTTnet.Client private void StartReceivingPackets(CancellationToken cancellationToken) { _packetReceiverTask = Task.Factory.StartNew( - () => ReceivePacketsAsync(cancellationToken), + () => ReceivePacketsAsync(cancellationToken), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); @@ -518,7 +513,7 @@ namespace MQTTnet.Client { _keepAliveMessageSenderTask = Task.Factory.StartNew( () => SendKeepAliveMessagesAsync(cancellationToken), - cancellationToken, + cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } @@ -539,7 +534,7 @@ namespace MQTTnet.Client } catch (Exception exception) { - _logger.Error(exception, "Unhandled exception while handling application message."); + _logger.Error(this, exception, "Unhandled exception while handling application message."); } } diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs index 6ed16de2..b7d85feb 100644 --- a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs @@ -8,14 +8,18 @@ namespace MQTTnet.Diagnostics void Verbose(string message, params object[] parameters); + void Verbose(object source, string message, params object[] parameters); + void Info(string message, params object[] parameters); + void Info(object source, string message, params object[] parameters); + void Warning(Exception exception, string message, params object[] parameters); - void Warning(string message, params object[] parameters); + void Warning(object source, Exception exception, string message, params object[] parameters); void Error(Exception exception, string message, params object[] parameters); - void Error(string message, params object[] parameters); + void Error(object source, Exception exception, string message, params object[] parameters); } } diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs index 53598b2b..94219216 100644 --- a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs @@ -15,35 +15,45 @@ namespace MQTTnet.Diagnostics public void Verbose(string message, params object[] parameters) { - Publish(MqttNetLogLevel.Verbose, null, message, parameters); + Publish(MqttNetLogLevel.Verbose, typeof(TSource), message, parameters, null); + } + + public void Verbose(object source, string message, params object[] parameters) + { + Publish(MqttNetLogLevel.Verbose, source, message, parameters, null); } public void Info(string message, params object[] parameters) { - Publish(MqttNetLogLevel.Info, null, message, parameters); + Publish(MqttNetLogLevel.Info, typeof(TSource), message, parameters, null); + } + + public void Info(object source, string message, params object[] parameters) + { + Publish(MqttNetLogLevel.Info, source, message, parameters, null); } public void Warning(Exception exception, string message, params object[] parameters) { - Publish(MqttNetLogLevel.Warning, exception, message, parameters); + Publish(MqttNetLogLevel.Warning, typeof(TSource), message, parameters, null); } - public void Warning(string message, params object[] parameters) + public void Warning(object source, Exception exception, string message, params object[] parameters) { - Warning(null, message, parameters); + Publish(MqttNetLogLevel.Warning, source, message, parameters, null); } public void Error(Exception exception, string message, params object[] parameters) { - Publish(MqttNetLogLevel.Error, exception, message, parameters); + Publish(MqttNetLogLevel.Error, typeof(TSource), message, parameters, null); } - public void Error(string message, params object[] parameters) + public void Error(object source, Exception exception, string message, params object[] parameters) { - Warning(null, message, parameters); + Publish(MqttNetLogLevel.Error, source, message, parameters, null); } - private void Publish(MqttNetLogLevel logLevel, Exception exception, string message, object[] parameters) + private void Publish(MqttNetLogLevel logLevel, object source, string message, object[] parameters, Exception exception) { var hasLocalListeners = LogMessagePublished != null; var hasGlobalListeners = MqttNetGlobalLogger.HasListeners; @@ -58,7 +68,13 @@ namespace MQTTnet.Diagnostics message = string.Format(message, parameters); } - var traceMessage = new MqttNetLogMessage(_logId, DateTime.Now, Environment.CurrentManagedThreadId, typeof(TSource).Name, logLevel, message, exception); + string sourceName = null; + if (source != null) + { + sourceName = source.GetType().Name; + } + + var traceMessage = new MqttNetLogMessage(_logId, DateTime.Now, Environment.CurrentManagedThreadId, sourceName, logLevel, message, exception); if (hasGlobalListeners) { diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs index 9ae7715f..36373192 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs @@ -6,6 +6,7 @@ namespace MQTTnet.Server { int ConnectionBacklog { get; } int MaxPendingMessagesPerClient { get; } + MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; } TimeSpan DefaultCommunicationTimeout { get; } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs index 8dc96e40..ac95807a 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs @@ -59,7 +59,7 @@ namespace MQTTnet.Server // Values described here: [MQTT-3.1.2-24]. if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D) { - _logger.Warning("Client '{0}': Did not receive any packet or keep alive signal.", _clientId); + _logger.Warning(this, null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientId); _timeoutCallback?.Invoke(); @@ -74,11 +74,11 @@ namespace MQTTnet.Server } catch (Exception exception) { - _logger.Error(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId); + _logger.Error(this, exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId); } finally { - _logger.Verbose("Client {0}: Stopped checking keep alive timeout.", _clientId); + _logger.Verbose(this, "Client {0}: Stopped checking keep alive timeout.", _clientId); } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs index fd89691d..a6e22049 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs @@ -50,25 +50,27 @@ namespace MQTTnet.Server } } - public async Task DropPacket() - { - MqttBasePacket packet = null; - await _queueWaitSemaphore.WaitAsync().ConfigureAwait(false); - if (!_queue.TryDequeue(out packet)) - { - throw new InvalidOperationException(); // should not happen - } - _queueWaitSemaphore.Release(); - } - public void Enqueue(MqttBasePacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); + if (_queue.Count >= _options.MaxPendingMessagesPerClient) + { + if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage) + { + return; + } + + if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage) + { + _queue.TryDequeue(out _); + } + } + _queue.Enqueue(packet); _queueAutoResetEvent.Set(); - _logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId); + _logger.Verbose(this, "Enqueued packet (ClientId: {0}).", _clientSession.ClientId); } private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) @@ -77,7 +79,7 @@ namespace MQTTnet.Server { while (!cancellationToken.IsCancellationRequested) { - await SendNextQueuedPacketAsync(adapter, cancellationToken).ConfigureAwait(false); + await TrySendNextQueuedPacketAsync(adapter, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) @@ -85,19 +87,23 @@ namespace MQTTnet.Server } catch (Exception exception) { - _logger.Error(exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _clientSession.ClientId); + _logger.Error(this, exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _clientSession.ClientId); } } - private async Task SendNextQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) + private async Task TrySendNextQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) { MqttBasePacket packet = null; try { - await _queueAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false); + if (_queue.IsEmpty) + { + await _queueAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false); + } + if (!_queue.TryDequeue(out packet)) { - throw new InvalidOperationException(); // should not happen + return; } if (cancellationToken.IsCancellationRequested) @@ -107,24 +113,25 @@ namespace MQTTnet.Server await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { packet }, cancellationToken).ConfigureAwait(false); - _logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); + _logger.Verbose("Enqueued packet sent (ClientId: {0}).", + _clientSession.ClientId); } catch (Exception exception) { if (exception is MqttCommunicationTimedOutException) { - _logger.Warning(exception, "Sending publish packet failed due to timeout (ClientId: {0}).", _clientSession.ClientId); + _logger.Warning(this, exception, "Sending publish packet failed: Timeout (ClientId: {0}).", _clientSession.ClientId); } else if (exception is MqttCommunicationException) { - _logger.Warning(exception, "Sending publish packet failed due to communication exception (ClientId: {0}).", _clientSession.ClientId); + _logger.Warning(this, exception, "Sending publish packet failed: Communication exception (ClientId: {0}).", _clientSession.ClientId); } else if (exception is OperationCanceledException) { } else { - _logger.Error(exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId); + _logger.Error(this, exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId); } if (packet is MqttPublishPacket publishPacket) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs index 7d50bad4..f4f8c995 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs @@ -147,10 +147,7 @@ namespace MQTTnet.Server { publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); } - if (_options.MaxPendingMessagesPerClient <= PendingMessagesQueue.Count) - { - await PendingMessagesQueue.DropPacket(); - } + PendingMessagesQueue.Enqueue(publishPacket); } @@ -276,7 +273,7 @@ namespace MQTTnet.Server return Task.FromResult(0); } - _logger.Warning("Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); + _logger.Warning(this, null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); Stop(MqttClientDisconnectType.NotClean); return Task.FromResult(0); diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttPendingMessagesOverflowStrategy.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttPendingMessagesOverflowStrategy.cs new file mode 100644 index 00000000..1601487e --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttPendingMessagesOverflowStrategy.cs @@ -0,0 +1,8 @@ +namespace MQTTnet.Server +{ + public enum MqttPendingMessagesOverflowStrategy + { + DropOldestQueuedMessage, + DropNewMessage + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs index 4315fcfb..e6117fde 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs @@ -11,7 +11,9 @@ namespace MQTTnet.Server public int ConnectionBacklog { get; set; } = 10; public int MaxPendingMessagesPerClient { get; set; } = 250; - + + public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage; + public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15); public Action ConnectionValidator { get; set; } From 8df8cb1eb6fc88923f6180aed62a43b9bd1fe1e0 Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 11 May 2018 22:00:54 +0200 Subject: [PATCH 042/138] Refactor logging and add benchmark. --- .../MQTTnet.AspnetCore/MqttHostedServer.cs | 2 +- .../MqttWebSocketServerAdapter.cs | 2 +- .../Adapter/MqttChannelAdapter.cs | 17 ++--- .../Client/IMqttClientAdapterFactory.cs | 2 +- .../MQTTnet.NetStandard/Client/MqttClient.cs | 46 +++++++------- .../Diagnostics/IMqttNetChildLogger.cs | 17 +++++ .../Diagnostics/IMqttNetLogger.cs | 16 +---- .../Diagnostics/MqttNetChildLogger.cs | 51 +++++++++++++++ .../Diagnostics/MqttNetLogger.cs | 62 +++++-------------- .../MqttClientAdapterFactory.cs | 2 +- .../MqttTcpServerAdapter.Uwp.cs | 10 +-- .../Implementations/MqttTcpServerAdapter.cs | 12 ++-- .../ManagedClient/ManagedMqttClient.cs | 31 +++++----- Frameworks/MQTTnet.NetStandard/MqttFactory.cs | 8 +-- .../Server/MqttClientKeepAliveMonitor.cs | 14 +++-- .../Server/MqttClientPendingMessagesQueue.cs | 21 ++++--- .../Server/MqttClientSession.cs | 27 ++++---- .../Server/MqttClientSessionsManager.cs | 48 +++++++------- .../Server/MqttRetainedMessagesManager.cs | 56 +++++++++-------- .../MQTTnet.NetStandard/Server/MqttServer.cs | 15 ++--- Tests/MQTTnet.Benchmarks/LoggerBenchmark.cs | 54 ++++++++++++++++ .../MQTTnet.Benchmarks.csproj | 1 + Tests/MQTTnet.Benchmarks/Program.cs | 5 +- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 2 - .../MqttKeepAliveMonitorTests.cs | 4 +- .../MqttSubscriptionsManagerTests.cs | 10 +-- .../TestMqttCommunicationAdapterFactory.cs | 2 +- 27 files changed, 318 insertions(+), 219 deletions(-) create mode 100644 Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetChildLogger.cs create mode 100644 Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetChildLogger.cs create mode 100644 Tests/MQTTnet.Benchmarks/LoggerBenchmark.cs diff --git a/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs b/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs index 8e6761ad..708d3832 100644 --- a/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs +++ b/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs @@ -13,7 +13,7 @@ namespace MQTTnet.AspNetCore { private readonly IMqttServerOptions _options; - public MqttHostedServer(IMqttServerOptions options, IEnumerable adapters, IMqttNetLogger logger) : base(adapters, logger) + public MqttHostedServer(IMqttServerOptions options, IEnumerable adapters, IMqttNetLogger logger) : base(adapters, logger.CreateChildLogger(nameof(MqttHostedServer))) { _options = options ?? throw new ArgumentNullException(nameof(options)); } diff --git a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index babc59b2..666c71ef 100644 --- a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -27,7 +27,7 @@ namespace MQTTnet.AspNetCore { if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); - var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket), new MqttPacketSerializer(), new MqttNetLogger()); + var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket), new MqttPacketSerializer(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter))); var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); ClientAccepted?.Invoke(this, eventArgs); diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index c531adde..9af60315 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -18,16 +18,19 @@ namespace MQTTnet.Adapter private const uint ErrorOperationAborted = 0x800703E3; private const int ReadBufferSize = 4096; // TODO: Move buffer size to config - private readonly IMqttNetLogger _logger; + private readonly IMqttNetChildLogger _logger; private readonly IMqttChannel _channel; private bool _isDisposed; - public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetLogger logger) + public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetChildLogger logger) { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + _channel = channel ?? throw new ArgumentNullException(nameof(channel)); PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + + _logger = logger.CreateChildLogger(nameof(MqttChannelAdapter)); } public IMqttPacketSerializer PacketSerializer { get; } @@ -35,7 +38,7 @@ namespace MQTTnet.Adapter public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken) { ThrowIfDisposed(); - _logger.Verbose("Connecting [Timeout={0}]", timeout); + _logger.Verbose("Connecting [Timeout={0}]", timeout); return ExecuteAndWrapExceptionAsync(() => Internal.TaskExtensions.TimeoutAfter(ct => _channel.ConnectAsync(ct), timeout, cancellationToken)); @@ -44,7 +47,7 @@ namespace MQTTnet.Adapter public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken) { ThrowIfDisposed(); - _logger.Verbose("Disconnecting [Timeout={0}]", timeout); + _logger.Verbose("Disconnecting [Timeout={0}]", timeout); return ExecuteAndWrapExceptionAsync(() => Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, cancellationToken)); @@ -69,7 +72,7 @@ namespace MQTTnet.Adapter { return ExecuteAndWrapExceptionAsync(() => { - _logger.Verbose("TX >>> {0} [Timeout={1}]", packet, timeout); + _logger.Verbose("TX >>> {0} [Timeout={1}]", packet, timeout); var packetData = PacketSerializer.Serialize(packet); @@ -111,7 +114,7 @@ namespace MQTTnet.Adapter throw new MqttProtocolViolationException("Received malformed packet."); } - _logger.Verbose("RX <<< {0}", packet); + _logger.Verbose("RX <<< {0}", packet); } finally { diff --git a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientAdapterFactory.cs b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientAdapterFactory.cs index c2a7e74b..197858b8 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientAdapterFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientAdapterFactory.cs @@ -5,6 +5,6 @@ namespace MQTTnet.Client { public interface IMqttClientAdapterFactory { - IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetLogger logger); + IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetChildLogger logger); } } diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index 4f28a90b..b342ddc2 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -21,7 +21,7 @@ namespace MQTTnet.Client private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); private readonly IMqttClientAdapterFactory _adapterFactory; - private readonly IMqttNetLogger _logger; + private readonly IMqttNetChildLogger _logger; private IMqttClientOptions _options; private CancellationTokenSource _cancellationTokenSource; @@ -31,8 +31,10 @@ namespace MQTTnet.Client public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger) { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + _adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _logger = logger.CreateChildLogger(nameof(MqttClient)); } public event EventHandler Connected; @@ -57,14 +59,14 @@ namespace MQTTnet.Client _adapter = _adapterFactory.CreateClientAdapter(options, _logger); - _logger.Verbose(this, "Trying to connect with server."); + _logger.Verbose("Trying to connect with server."); await _adapter.ConnectAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false); - _logger.Verbose(this, "Connection with server established."); + _logger.Verbose("Connection with server established."); StartReceivingPackets(_cancellationTokenSource.Token); var connectResponse = await AuthenticateAsync(options.WillMessage, _cancellationTokenSource.Token).ConfigureAwait(false); - _logger.Verbose(this, "MQTT connection with server established."); + _logger.Verbose("MQTT connection with server established."); _sendTracker.Restart(); @@ -76,12 +78,12 @@ namespace MQTTnet.Client IsConnected = true; Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent)); - _logger.Info(this, "Connected."); + _logger.Info("Connected."); return new MqttClientConnectResult(connectResponse.IsSessionPresent); } catch (Exception exception) { - _logger.Error(this, exception, "Error while connecting with server."); + _logger.Error(exception, "Error while connecting with server."); await DisconnectInternalAsync(null, exception).ConfigureAwait(false); throw; @@ -245,7 +247,7 @@ namespace MQTTnet.Client } catch (Exception adapterException) { - _logger.Warning(this, adapterException, "Error while disconnecting from adapter."); + _logger.Warning(adapterException, "Error while disconnecting from adapter."); } finally { @@ -265,11 +267,11 @@ namespace MQTTnet.Client await _adapter.DisconnectAsync(_options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false); } - _logger.Verbose(this, "Disconnected from adapter."); + _logger.Verbose("Disconnected from adapter."); } catch (Exception adapterException) { - _logger.Warning(this, adapterException, "Error while disconnecting from adapter."); + _logger.Warning(adapterException, "Error while disconnecting from adapter."); } finally { @@ -278,7 +280,7 @@ namespace MQTTnet.Client _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; - _logger.Info(this, "Disconnected."); + _logger.Info("Disconnected."); Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception)); } } @@ -324,7 +326,7 @@ namespace MQTTnet.Client } catch (MqttCommunicationTimedOutException) { - _logger.Warning(this, null, "Timeout while waiting for packet of type '{0}'.", typeof(TResponsePacket).Namespace); + _logger.Warning(null, "Timeout while waiting for packet of type '{0}'.", typeof(TResponsePacket).Namespace); throw; } finally @@ -335,7 +337,7 @@ namespace MQTTnet.Client private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken) { - _logger.Verbose(this, "Start sending keep alive packets."); + _logger.Verbose("Start sending keep alive packets."); try { @@ -362,24 +364,24 @@ namespace MQTTnet.Client } else if (exception is MqttCommunicationException) { - _logger.Warning(this, exception, "MQTT communication exception while sending/receiving keep alive packets."); + _logger.Warning(exception, "MQTT communication exception while sending/receiving keep alive packets."); } else { - _logger.Error(this, exception, "Unhandled exception while sending/receiving keep alive packets."); + _logger.Error(exception, "Unhandled exception while sending/receiving keep alive packets."); } await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false); } finally { - _logger.Verbose(this, "Stopped sending keep alive packets."); + _logger.Verbose("Stopped sending keep alive packets."); } } private async Task ReceivePacketsAsync(CancellationToken cancellationToken) { - _logger.Verbose(this, "Start receiving packets."); + _logger.Verbose("Start receiving packets."); try { @@ -414,11 +416,11 @@ namespace MQTTnet.Client } else if (exception is MqttCommunicationException) { - _logger.Warning(this, exception, "MQTT communication exception while receiving packets."); + _logger.Warning(exception, "MQTT communication exception while receiving packets."); } else { - _logger.Error(this, exception, "Unhandled exception while receiving packets."); + _logger.Error(exception, "Unhandled exception while receiving packets."); } await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false); @@ -426,7 +428,7 @@ namespace MQTTnet.Client } finally { - _logger.Verbose(this, "Stopped receiving packets."); + _logger.Verbose("Stopped receiving packets."); } } @@ -462,7 +464,7 @@ namespace MQTTnet.Client } catch (Exception exception) { - _logger.Error(this, exception, "Unhandled exception while processing received packet."); + _logger.Error(exception, "Unhandled exception while processing received packet."); } } @@ -534,7 +536,7 @@ namespace MQTTnet.Client } catch (Exception exception) { - _logger.Error(this, exception, "Unhandled exception while handling application message."); + _logger.Error(exception, "Unhandled exception while handling application message."); } } diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetChildLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetChildLogger.cs new file mode 100644 index 00000000..55d99e5d --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetChildLogger.cs @@ -0,0 +1,17 @@ +using System; + +namespace MQTTnet.Diagnostics +{ + public interface IMqttNetChildLogger + { + IMqttNetChildLogger CreateChildLogger(string source); + + void Verbose(string message, params object[] parameters); + + void Info(string message, params object[] parameters); + + void Warning(Exception exception, string message, params object[] parameters); + + void Error(Exception exception, string message, params object[] parameters); + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs index b7d85feb..d2aacf40 100644 --- a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs @@ -6,20 +6,8 @@ namespace MQTTnet.Diagnostics { event EventHandler LogMessagePublished; - void Verbose(string message, params object[] parameters); + IMqttNetChildLogger CreateChildLogger(string source); - void Verbose(object source, string message, params object[] parameters); - - void Info(string message, params object[] parameters); - - void Info(object source, string message, params object[] parameters); - - void Warning(Exception exception, string message, params object[] parameters); - - void Warning(object source, Exception exception, string message, params object[] parameters); - - void Error(Exception exception, string message, params object[] parameters); - - void Error(object source, Exception exception, string message, params object[] parameters); + void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception); } } diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetChildLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetChildLogger.cs new file mode 100644 index 00000000..50557319 --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetChildLogger.cs @@ -0,0 +1,51 @@ +using System; + +namespace MQTTnet.Diagnostics +{ + public sealed class MqttNetChildLogger : IMqttNetChildLogger + { + private readonly IMqttNetLogger _logger; + private readonly string _source; + + public MqttNetChildLogger(IMqttNetLogger logger, string source) + { + _logger = logger; + _source = source; + } + + public IMqttNetChildLogger CreateChildLogger(string source) + { + string childSource; + if (!string.IsNullOrEmpty(_source)) + { + childSource = _source + "." + source; + } + else + { + childSource = source; + } + + return new MqttNetChildLogger(_logger, childSource); + } + + public void Verbose(string message, params object[] parameters) + { + _logger.Publish(MqttNetLogLevel.Verbose, _source, message, parameters, null); + } + + public void Info(string message, params object[] parameters) + { + _logger.Publish(MqttNetLogLevel.Info, _source, message, parameters, null); + } + + public void Warning(Exception exception, string message, params object[] parameters) + { + _logger.Publish(MqttNetLogLevel.Warning, _source, message, parameters, exception); + } + + public void Error(Exception exception, string message, params object[] parameters) + { + _logger.Publish(MqttNetLogLevel.Error, _source, message, parameters, exception); + } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs index 94219216..868e5d7c 100644 --- a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Diagnostics { - public class MqttNetLogger : IMqttNetLogger + public sealed class MqttNetLogger : IMqttNetLogger { private readonly string _logId; @@ -13,47 +13,12 @@ namespace MQTTnet.Diagnostics public event EventHandler LogMessagePublished; - public void Verbose(string message, params object[] parameters) + public IMqttNetChildLogger CreateChildLogger(string source) { - Publish(MqttNetLogLevel.Verbose, typeof(TSource), message, parameters, null); + return new MqttNetChildLogger(this, source); } - public void Verbose(object source, string message, params object[] parameters) - { - Publish(MqttNetLogLevel.Verbose, source, message, parameters, null); - } - - public void Info(string message, params object[] parameters) - { - Publish(MqttNetLogLevel.Info, typeof(TSource), message, parameters, null); - } - - public void Info(object source, string message, params object[] parameters) - { - Publish(MqttNetLogLevel.Info, source, message, parameters, null); - } - - public void Warning(Exception exception, string message, params object[] parameters) - { - Publish(MqttNetLogLevel.Warning, typeof(TSource), message, parameters, null); - } - - public void Warning(object source, Exception exception, string message, params object[] parameters) - { - Publish(MqttNetLogLevel.Warning, source, message, parameters, null); - } - - public void Error(Exception exception, string message, params object[] parameters) - { - Publish(MqttNetLogLevel.Error, typeof(TSource), message, parameters, null); - } - - public void Error(object source, Exception exception, string message, params object[] parameters) - { - Publish(MqttNetLogLevel.Error, source, message, parameters, null); - } - - private void Publish(MqttNetLogLevel logLevel, object source, string message, object[] parameters, Exception exception) + public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception) { var hasLocalListeners = LogMessagePublished != null; var hasGlobalListeners = MqttNetGlobalLogger.HasListeners; @@ -63,18 +28,19 @@ namespace MQTTnet.Diagnostics return; } - if (parameters.Length > 0) + if (parameters?.Length > 0) { - message = string.Format(message, parameters); + try + { + message = string.Format(message, parameters); + } + catch + { + message = "MESSAGE FORMAT INVALID: " + message; + } } - string sourceName = null; - if (source != null) - { - sourceName = source.GetType().Name; - } - - var traceMessage = new MqttNetLogMessage(_logId, DateTime.Now, Environment.CurrentManagedThreadId, sourceName, logLevel, message, exception); + var traceMessage = new MqttNetLogMessage(_logId, DateTime.UtcNow, Environment.CurrentManagedThreadId, source, logLevel, message, exception); if (hasGlobalListeners) { diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs index 4692d7ee..5256da38 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs @@ -8,7 +8,7 @@ namespace MQTTnet.Implementations { public class MqttClientAdapterFactory : IMqttClientAdapterFactory { - public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetLogger logger) + public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetChildLogger logger) { if (options == null) throw new ArgumentNullException(nameof(options)); diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs index b2c31103..26bae2e1 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs @@ -11,12 +11,14 @@ namespace MQTTnet.Implementations { public class MqttTcpServerAdapter : IMqttServerAdapter { - private readonly IMqttNetLogger _logger; + private readonly IMqttNetChildLogger _logger; private StreamSocketListener _defaultEndpointSocket; - public MqttTcpServerAdapter(IMqttNetLogger logger) + public MqttTcpServerAdapter(IMqttNetChildLogger logger) { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + _logger = logger.CreateChildLogger(nameof(MqttTcpServerAdapter)); } public event EventHandler ClientAccepted; @@ -74,7 +76,7 @@ namespace MQTTnet.Implementations } catch (Exception exception) { - _logger.Error(exception, "Error while accepting connection at default endpoint."); + _logger.Error(exception, "Error while accepting connection at default endpoint."); } } } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs index 0404de36..85c15cec 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs @@ -16,16 +16,18 @@ namespace MQTTnet.Implementations { public class MqttTcpServerAdapter : IMqttServerAdapter { - private readonly IMqttNetLogger _logger; + private readonly IMqttNetChildLogger _logger; private CancellationTokenSource _cancellationTokenSource; private Socket _defaultEndpointSocket; private Socket _tlsEndpointSocket; private X509Certificate2 _tlsCertificate; - public MqttTcpServerAdapter(IMqttNetLogger logger) + public MqttTcpServerAdapter(IMqttNetChildLogger logger) { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + _logger = logger.CreateChildLogger(nameof(MqttTcpServerAdapter)); } public event EventHandler ClientAccepted; @@ -127,7 +129,7 @@ namespace MQTTnet.Implementations return; } - _logger.Error(exception, "Error while accepting connection at default endpoint."); + _logger.Error(exception, "Error while accepting connection at default endpoint."); await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); } } @@ -162,7 +164,7 @@ namespace MQTTnet.Implementations return; } - _logger.Error(exception, "Error while accepting connection at TLS endpoint."); + _logger.Error(exception, "Error while accepting connection at TLS endpoint."); await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); } } diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs index bbfc1c78..af9cc836 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs @@ -19,7 +19,7 @@ namespace MQTTnet.ManagedClient private readonly List _unsubscriptions = new List(); private readonly IMqttClient _mqttClient; - private readonly IMqttNetLogger _logger; + private readonly IMqttNetChildLogger _logger; private CancellationTokenSource _connectionCancellationToken; private CancellationTokenSource _publishingCancellationToken; @@ -29,14 +29,17 @@ namespace MQTTnet.ManagedClient private bool _subscriptionsNotPushed; - public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger) + public ManagedMqttClient(IMqttClient mqttClient, IMqttNetChildLogger logger) { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); _mqttClient.Connected += OnConnected; _mqttClient.Disconnected += OnDisconnected; _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; + + _logger = logger.CreateChildLogger(nameof(ManagedMqttClient)); } public bool IsConnected => _mqttClient.IsConnected; @@ -74,7 +77,7 @@ namespace MQTTnet.ManagedClient Task.Run(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - _logger.Info("Started"); + _logger.Info("Started"); } public Task StopAsync() @@ -166,12 +169,12 @@ namespace MQTTnet.ManagedClient } catch (Exception exception) { - _logger.Error(exception, "Unhandled exception while maintaining connection."); + _logger.Error(exception, "Unhandled exception while maintaining connection."); } finally { await _mqttClient.DisconnectAsync().ConfigureAwait(false); - _logger.Info("Stopped"); + _logger.Info("Stopped"); } } @@ -204,11 +207,11 @@ namespace MQTTnet.ManagedClient } catch (MqttCommunicationException exception) { - _logger.Warning(exception, "Communication exception while maintaining connection."); + _logger.Warning(exception, "Communication exception while maintaining connection."); } catch (Exception exception) { - _logger.Error(exception, "Unhandled exception while maintaining connection."); + _logger.Error(exception, "Unhandled exception while maintaining connection."); } } @@ -237,11 +240,11 @@ namespace MQTTnet.ManagedClient } catch (Exception exception) { - _logger.Error(exception, "Unhandled exception while publishing queued application messages."); + _logger.Error(exception, "Unhandled exception while publishing queued application messages."); } finally { - _logger.Verbose("Stopped publishing messages."); + _logger.Verbose("Stopped publishing messages."); } } @@ -261,7 +264,7 @@ namespace MQTTnet.ManagedClient { transmitException = exception; - _logger.Warning(exception, "Publishing application message failed."); + _logger.Warning(exception, "Publishing application message failed."); if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { @@ -271,7 +274,7 @@ namespace MQTTnet.ManagedClient catch (Exception exception) { transmitException = exception; - _logger.Error(exception, "Unhandled exception while publishing queued application message."); + _logger.Error(exception, "Unhandled exception while publishing queued application message."); } finally { @@ -281,7 +284,7 @@ namespace MQTTnet.ManagedClient private async Task SynchronizeSubscriptionsAsync() { - _logger.Info(nameof(ManagedMqttClient), "Synchronizing subscriptions"); + _logger.Info(nameof(ManagedMqttClient), "Synchronizing subscriptions"); List subscriptions; List unsubscriptions; @@ -320,7 +323,7 @@ namespace MQTTnet.ManagedClient } catch (Exception exception) { - _logger.Warning(exception, "Synchronizing subscriptions failed."); + _logger.Warning(exception, "Synchronizing subscriptions failed."); _subscriptionsNotPushed = true; SynchronizingSubscriptionsFailed?.Invoke(this, EventArgs.Empty); diff --git a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs index e3b3549a..d9aa9443 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs @@ -25,14 +25,14 @@ namespace MQTTnet public IManagedMqttClient CreateManagedMqttClient() { - return new ManagedMqttClient(CreateMqttClient(), new MqttNetLogger()); + return new ManagedMqttClient(CreateMqttClient(), new MqttNetLogger().CreateChildLogger(string.Empty)); } public IManagedMqttClient CreateManagedMqttClient(IMqttNetLogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); - return new ManagedMqttClient(CreateMqttClient(), logger); + return new ManagedMqttClient(CreateMqttClient(), logger.CreateChildLogger(string.Empty)); } public IMqttServer CreateMqttServer() @@ -45,7 +45,7 @@ namespace MQTTnet { if (logger == null) throw new ArgumentNullException(nameof(logger)); - return CreateMqttServer(new List { new MqttTcpServerAdapter(logger) }, logger); + return CreateMqttServer(new List { new MqttTcpServerAdapter(logger.CreateChildLogger(string.Empty)) }, logger); } public IMqttServer CreateMqttServer(IEnumerable adapters, IMqttNetLogger logger) @@ -53,7 +53,7 @@ namespace MQTTnet if (adapters == null) throw new ArgumentNullException(nameof(adapters)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - return new MqttServer(adapters, logger); + return new MqttServer(adapters, logger.CreateChildLogger(string.Empty)); } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs index ac95807a..46196008 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs @@ -14,15 +14,17 @@ namespace MQTTnet.Server private readonly string _clientId; private readonly Action _timeoutCallback; - private readonly IMqttNetLogger _logger; + private readonly IMqttNetChildLogger _logger; private Task _workerTask; - public MqttClientKeepAliveMonitor(string clientId, Action timeoutCallback, IMqttNetLogger logger) + public MqttClientKeepAliveMonitor(string clientId, Action timeoutCallback, IMqttNetChildLogger logger) { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + _clientId = clientId; _timeoutCallback = timeoutCallback; - _logger = logger; + _logger = logger.CreateChildLogger(nameof(MqttClientKeepAliveMonitor)); } public TimeSpan LastPacketReceived => _lastPacketReceivedTracker.Elapsed; @@ -59,7 +61,7 @@ namespace MQTTnet.Server // Values described here: [MQTT-3.1.2-24]. if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D) { - _logger.Warning(this, null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientId); + _logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientId); _timeoutCallback?.Invoke(); @@ -74,11 +76,11 @@ namespace MQTTnet.Server } catch (Exception exception) { - _logger.Error(this, exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId); + _logger.Error(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId); } finally { - _logger.Verbose(this, "Client {0}: Stopped checking keep alive timeout.", _clientId); + _logger.Verbose("Client {0}: Stopped checking keep alive timeout.", _clientId); } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs index a6e22049..fc74d4c5 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs @@ -17,15 +17,17 @@ namespace MQTTnet.Server private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent(); private readonly IMqttServerOptions _options; private readonly MqttClientSession _clientSession; - private readonly IMqttNetLogger _logger; + private readonly IMqttNetChildLogger _logger; private Task _workerTask; - public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetLogger logger) + public MqttClientPendingMessagesQueue(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 ?? throw new ArgumentNullException(nameof(logger)); + + _logger = logger.CreateChildLogger(nameof(MqttClientPendingMessagesQueue)); } public int Count => _queue.Count; @@ -70,7 +72,7 @@ namespace MQTTnet.Server _queue.Enqueue(packet); _queueAutoResetEvent.Set(); - _logger.Verbose(this, "Enqueued packet (ClientId: {0}).", _clientSession.ClientId); + _logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId); } private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) @@ -87,7 +89,7 @@ namespace MQTTnet.Server } catch (Exception exception) { - _logger.Error(this, exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _clientSession.ClientId); + _logger.Error(exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _clientSession.ClientId); } } @@ -113,25 +115,24 @@ namespace MQTTnet.Server await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { packet }, cancellationToken).ConfigureAwait(false); - _logger.Verbose("Enqueued packet sent (ClientId: {0}).", - _clientSession.ClientId); + _logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); } catch (Exception exception) { if (exception is MqttCommunicationTimedOutException) { - _logger.Warning(this, exception, "Sending publish packet failed: Timeout (ClientId: {0}).", _clientSession.ClientId); + _logger.Warning(exception, "Sending publish packet failed: Timeout (ClientId: {0}).", _clientSession.ClientId); } else if (exception is MqttCommunicationException) { - _logger.Warning(this, exception, "Sending publish packet failed: Communication exception (ClientId: {0}).", _clientSession.ClientId); + _logger.Warning(exception, "Sending publish packet failed: Communication exception (ClientId: {0}).", _clientSession.ClientId); } else if (exception is OperationCanceledException) { } else { - _logger.Error(this, exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId); + _logger.Error(exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId); } if (packet is MqttPublishPacket publishPacket) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs index f4f8c995..1d0b77d4 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs @@ -17,7 +17,7 @@ namespace MQTTnet.Server { private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); private readonly MqttRetainedMessagesManager _retainedMessagesManager; - private readonly IMqttNetLogger _logger; + private readonly IMqttNetChildLogger _logger; private readonly IMqttServerOptions _options; private readonly MqttClientSessionsManager _sessionsManager; @@ -30,15 +30,18 @@ namespace MQTTnet.Server IMqttServerOptions options, MqttClientSessionsManager sessionsManager, MqttRetainedMessagesManager retainedMessagesManager, - IMqttNetLogger logger) + IMqttNetChildLogger logger) { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + _options = options ?? throw new ArgumentNullException(nameof(options)); _sessionsManager = sessionsManager; _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - + ClientId = clientId; + _logger = logger.CreateChildLogger(nameof(MqttClientSession)); + KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeout, _logger); SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server); PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); @@ -81,13 +84,11 @@ namespace MQTTnet.Server } catch (MqttCommunicationException exception) { - _logger.Warning(exception, - "Client '{0}': Communication exception while processing client packets.", ClientId); + _logger.Warning(exception, "Client '{0}': Communication exception while processing client packets.", ClientId); } catch (Exception exception) { - _logger.Error(exception, - "Client '{0}': Unhandled exception while processing client packets.", ClientId); + _logger.Error(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); } finally { @@ -126,7 +127,7 @@ namespace MQTTnet.Server } finally { - _logger.Info("Client '{0}': Session stopped.", ClientId); + _logger.Info("Client '{0}': Session stopped.", ClientId); } } @@ -185,7 +186,7 @@ namespace MQTTnet.Server private void StopDueToKeepAliveTimeout() { - _logger.Info("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId); + _logger.Info("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId); Stop(MqttClientDisconnectType.NotClean); } @@ -207,11 +208,11 @@ namespace MQTTnet.Server { if (exception is MqttCommunicationException) { - _logger.Warning(exception, "Client '{0}': Communication exception while processing client packets.", ClientId); + _logger.Warning(exception, "Client '{0}': Communication exception while processing client packets.", ClientId); } else { - _logger.Error(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); + _logger.Error(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); } Stop(MqttClientDisconnectType.NotClean); @@ -273,7 +274,7 @@ namespace MQTTnet.Server return Task.FromResult(0); } - _logger.Warning(this, null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); + _logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); Stop(MqttClientDisconnectType.NotClean); return Task.FromResult(0); diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index 98e58350..e50b8c24 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; +using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -14,15 +15,18 @@ namespace MQTTnet.Server public sealed class MqttClientSessionsManager : IDisposable { private readonly Dictionary _sessions = new Dictionary(); - private readonly SemaphoreSlim _sessionsLock = new SemaphoreSlim(1, 1); + private readonly AsyncLock _sessionsLock = new AsyncLock(); private readonly MqttRetainedMessagesManager _retainedMessagesManager; private readonly IMqttServerOptions _options; - private readonly IMqttNetLogger _logger; + private readonly IMqttNetChildLogger _logger; - public MqttClientSessionsManager(IMqttServerOptions options, MqttServer server, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger) + public MqttClientSessionsManager(IMqttServerOptions options, MqttServer server, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetChildLogger logger) { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + _logger = logger.CreateChildLogger(nameof(MqttClientSessionsManager)); + _options = options ?? throw new ArgumentNullException(nameof(options)); Server = server ?? throw new ArgumentNullException(nameof(server)); _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); @@ -89,7 +93,7 @@ namespace MQTTnet.Server } catch (Exception exception) { - _logger.Error(exception, exception.Message); + _logger.Error(exception, exception.Message); } finally { @@ -100,7 +104,7 @@ namespace MQTTnet.Server } catch (Exception exception) { - _logger.Error(exception, exception.Message); + _logger.Error(exception, exception.Message); } Server.OnClientDisconnected(new ConnectedMqttClient @@ -115,7 +119,7 @@ namespace MQTTnet.Server public async Task StopAsync() { - await _sessionsLock.WaitAsync().ConfigureAwait(false); + await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { foreach (var session in _sessions) @@ -127,13 +131,13 @@ namespace MQTTnet.Server } finally { - _sessionsLock.Release(); + _sessionsLock.Exit(); } } public async Task> GetConnectedClientsAsync() { - await _sessionsLock.WaitAsync().ConfigureAwait(false); + await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient @@ -147,7 +151,7 @@ namespace MQTTnet.Server } finally { - _sessionsLock.Release(); + _sessionsLock.Exit(); } } @@ -161,7 +165,7 @@ namespace MQTTnet.Server if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - await _sessionsLock.WaitAsync().ConfigureAwait(false); + await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { if (!_sessions.TryGetValue(clientId, out var session)) @@ -173,7 +177,7 @@ namespace MQTTnet.Server } finally { - _sessionsLock.Release(); + _sessionsLock.Exit(); } } @@ -182,7 +186,7 @@ namespace MQTTnet.Server if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - await _sessionsLock.WaitAsync().ConfigureAwait(false); + await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { if (!_sessions.TryGetValue(clientId, out var session)) @@ -194,7 +198,7 @@ namespace MQTTnet.Server } finally { - _sessionsLock.Release(); + _sessionsLock.Exit(); } } @@ -222,7 +226,7 @@ namespace MQTTnet.Server private async Task PrepareClientSessionAsync(MqttConnectPacket connectPacket) { - await _sessionsLock.WaitAsync().ConfigureAwait(false); + await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); @@ -236,11 +240,11 @@ namespace MQTTnet.Server clientSession.Dispose(); clientSession = null; - _logger.Verbose("Stopped existing session of client '{0}'.", connectPacket.ClientId); + _logger.Verbose("Stopped existing session of client '{0}'.", connectPacket.ClientId); } else { - _logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId); + _logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId); } } @@ -252,14 +256,14 @@ namespace MQTTnet.Server clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _retainedMessagesManager, _logger); _sessions[connectPacket.ClientId] = clientSession; - _logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId); + _logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId); } return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession }; } finally { - _sessionsLock.Release(); + _sessionsLock.Exit(); } } @@ -287,10 +291,10 @@ namespace MQTTnet.Server } catch (Exception exception) { - _logger.Error(exception, "Error while processing application message"); + _logger.Error(exception, "Error while processing application message"); } - await _sessionsLock.WaitAsync().ConfigureAwait(false); + await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { foreach (var clientSession in _sessions.Values) @@ -300,7 +304,7 @@ namespace MQTTnet.Server } finally { - _sessionsLock.Release(); + _sessionsLock.Exit(); } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs index f998557e..be0a5b7b 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs @@ -4,19 +4,21 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Diagnostics; +using MQTTnet.Internal; namespace MQTTnet.Server { public sealed class MqttRetainedMessagesManager : IDisposable { - private readonly Dictionary _retainedMessages = new Dictionary(); - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); - private readonly IMqttNetLogger _logger; + private readonly Dictionary _messages = new Dictionary(); + private readonly AsyncLock _messagesLock = new AsyncLock(); + private readonly IMqttNetChildLogger _logger; private readonly IMqttServerOptions _options; - public MqttRetainedMessagesManager(IMqttServerOptions options, IMqttNetLogger logger) + public MqttRetainedMessagesManager(IMqttServerOptions options, IMqttNetChildLogger logger) { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + _logger = logger.CreateChildLogger(nameof(MqttRetainedMessagesManager)); _options = options ?? throw new ArgumentNullException(nameof(options)); } @@ -27,24 +29,24 @@ namespace MQTTnet.Server return; } - await _semaphore.WaitAsync().ConfigureAwait(false); + await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { - var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync(); + var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync().ConfigureAwait(false); - _retainedMessages.Clear(); + _messages.Clear(); foreach (var retainedMessage in retainedMessages) { - _retainedMessages[retainedMessage.Topic] = retainedMessage; + _messages[retainedMessage.Topic] = retainedMessage; } } catch (Exception exception) { - _logger.Error(exception, "Unhandled exception while loading retained messages."); + _logger.Error(exception, "Unhandled exception while loading retained messages."); } finally { - _semaphore.Release(); + _messagesLock.Exit(); } } @@ -52,18 +54,18 @@ namespace MQTTnet.Server { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - await _semaphore.WaitAsync().ConfigureAwait(false); + await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { await HandleMessageInternalAsync(clientId, applicationMessage); } catch (Exception exception) { - _logger.Error(exception, "Unhandled exception while handling retained messages."); + _logger.Error(exception, "Unhandled exception while handling retained messages."); } finally { - _semaphore.Release(); + _messagesLock.Exit(); } } @@ -71,10 +73,10 @@ namespace MQTTnet.Server { var retainedMessages = new List(); - await _semaphore.WaitAsync().ConfigureAwait(false); + await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { - foreach (var retainedMessage in _retainedMessages.Values) + foreach (var retainedMessage in _messages.Values) { foreach (var topicFilter in topicFilters) { @@ -90,7 +92,7 @@ namespace MQTTnet.Server } finally { - _semaphore.Release(); + _messagesLock.Exit(); } return retainedMessages; @@ -98,7 +100,7 @@ namespace MQTTnet.Server public void Dispose() { - _semaphore?.Dispose(); + _messagesLock?.Dispose(); } private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage) @@ -107,37 +109,37 @@ namespace MQTTnet.Server if (applicationMessage.Payload?.Any() == false) { - saveIsRequired = _retainedMessages.Remove(applicationMessage.Topic); - _logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); + saveIsRequired = _messages.Remove(applicationMessage.Topic); + _logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); } else { - if (!_retainedMessages.ContainsKey(applicationMessage.Topic)) + if (!_messages.ContainsKey(applicationMessage.Topic)) { - _retainedMessages[applicationMessage.Topic] = applicationMessage; + _messages[applicationMessage.Topic] = applicationMessage; saveIsRequired = true; } else { - var existingMessage = _retainedMessages[applicationMessage.Topic]; + var existingMessage = _messages[applicationMessage.Topic]; if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0])) { - _retainedMessages[applicationMessage.Topic] = applicationMessage; + _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) { - _logger.Verbose("Skipped saving retained messages because no changes were detected."); + _logger.Verbose("Skipped saving retained messages because no changes were detected."); } if (saveIsRequired && _options.Storage != null) { - await _options.Storage.SaveRetainedMessagesAsync(_retainedMessages.Values.ToList()); + await _options.Storage.SaveRetainedMessagesAsync(_messages.Values.ToList()).ConfigureAwait(false); } } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs index 03241344..a10d19c4 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs @@ -11,16 +11,17 @@ namespace MQTTnet.Server public class MqttServer : IMqttServer { private readonly ICollection _adapters; - private readonly IMqttNetLogger _logger; + private readonly IMqttNetChildLogger _logger; private MqttClientSessionsManager _clientSessionsManager; private MqttRetainedMessagesManager _retainedMessagesManager; private CancellationTokenSource _cancellationTokenSource; - public MqttServer(IEnumerable adapters, IMqttNetLogger logger) + public MqttServer(IEnumerable adapters, IMqttNetChildLogger logger) { if (adapters == null) throw new ArgumentNullException(nameof(adapters)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + _logger = logger.CreateChildLogger(nameof(MqttServer)); _adapters = adapters.ToList(); } @@ -90,7 +91,7 @@ namespace MQTTnet.Server await adapter.StartAsync(Options); } - _logger.Info("Started."); + _logger.Info("Started."); Started?.Invoke(this, new MqttServerStartedEventArgs()); } @@ -114,7 +115,7 @@ namespace MQTTnet.Server await _clientSessionsManager.StopAsync(); - _logger.Info("Stopped."); + _logger.Info("Stopped."); } finally { @@ -129,13 +130,13 @@ namespace MQTTnet.Server internal void OnClientConnected(ConnectedMqttClient client) { - _logger.Info("Client '{0}': Connected.", client.ClientId); + _logger.Info("Client '{0}': Connected.", client.ClientId); ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client)); } internal void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect) { - _logger.Info("Client '{0}': Disconnected (clean={1}).", client.ClientId, wasCleanDisconnect); + _logger.Info("Client '{0}': Disconnected (clean={1}).", client.ClientId, wasCleanDisconnect); ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client, wasCleanDisconnect)); } diff --git a/Tests/MQTTnet.Benchmarks/LoggerBenchmark.cs b/Tests/MQTTnet.Benchmarks/LoggerBenchmark.cs new file mode 100644 index 00000000..2dce560a --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/LoggerBenchmark.cs @@ -0,0 +1,54 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Exporters; +using BenchmarkDotNet.Attributes.Jobs; +using MQTTnet.Diagnostics; + +namespace MQTTnet.Benchmarks +{ + [ClrJob] + [RPlotExporter] + [MemoryDiagnoser] + public class LoggerBenchmark + { + private IMqttNetLogger _logger; + private IMqttNetChildLogger _childLogger; + private bool _useHandler; + + [GlobalSetup] + public void Setup() + { + _logger = new MqttNetLogger("1"); + _childLogger = _logger.CreateChildLogger("child"); + + MqttNetGlobalLogger.LogMessagePublished += OnLogMessagePublished; + } + + private void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs) + { + if (_useHandler) + { + eventArgs.TraceMessage.ToString(); + } + } + + [Benchmark] + public void Log_10000_Messages_NoHandler() + { + _useHandler = false; + for (var i = 0; i < 10000; i++) + { + _childLogger.Verbose("test log message {0}", "parameter"); + } + } + + [Benchmark] + public void Log_10000_Messages_WithHandler() + { + _useHandler = true; + for (var i = 0; i < 10000; i++) + { + _childLogger.Verbose("test log message {0}", "parameter"); + } + } + } +} diff --git a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index 3b52bbae..e068fa9d 100644 --- a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -147,6 +147,7 @@ + diff --git a/Tests/MQTTnet.Benchmarks/Program.cs b/Tests/MQTTnet.Benchmarks/Program.cs index ad2c3630..363cf8f2 100644 --- a/Tests/MQTTnet.Benchmarks/Program.cs +++ b/Tests/MQTTnet.Benchmarks/Program.cs @@ -1,5 +1,4 @@ using System; -using System.Threading; using BenchmarkDotNet.Running; using MQTTnet.Diagnostics; @@ -12,6 +11,7 @@ namespace MQTTnet.Benchmarks Console.WriteLine($"MQTTnet - BenchmarkApp.{TargetFrameworkInfoProvider.TargetFramework}"); Console.WriteLine("1 = MessageProcessingBenchmark"); Console.WriteLine("2 = SerializerBenchmark"); + Console.WriteLine("3 = LoggerBenchmark"); var pressedKey = Console.ReadKey(true); switch (pressedKey.KeyChar) @@ -22,7 +22,8 @@ namespace MQTTnet.Benchmarks case '2': BenchmarkRunner.Run(); break; - default: + case '3': + BenchmarkRunner.Run(); break; } diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index bcfff1c3..fdc03b68 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -1,9 +1,7 @@ using BenchmarkDotNet.Attributes; -using MQTTnet.Client; using MQTTnet.Packets; using MQTTnet.Serializer; using MQTTnet.Internal; -using MQTTnet.Server; using BenchmarkDotNet.Attributes.Jobs; using BenchmarkDotNet.Attributes.Exporters; using System; diff --git a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs index f3c43203..9563a41f 100644 --- a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs @@ -17,7 +17,7 @@ namespace MQTTnet.Core.Tests var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate { timeoutCalledCount++; - }, new MqttNetLogger()); + }, new MqttNetLogger().CreateChildLogger("")); Assert.AreEqual(0, timeoutCalledCount); @@ -38,7 +38,7 @@ namespace MQTTnet.Core.Tests var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate { timeoutCalledCount++; - }, new MqttNetLogger()); + }, new MqttNetLogger().CreateChildLogger("")); Assert.AreEqual(0, timeoutCalledCount); diff --git a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs index 6f16ab5d..cf922a15 100644 --- a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs @@ -13,7 +13,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeSingleSuccess() { - var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger())); + var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger().CreateChildLogger(""))); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); @@ -34,7 +34,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeDifferentQoSSuccess() { - var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger())); + var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger().CreateChildLogger(""))); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtMostOnce)); @@ -55,7 +55,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeTwoTimesSuccess() { - var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger())); + var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger().CreateChildLogger(""))); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)); @@ -77,7 +77,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeSingleNoSuccess() { - var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger())); + var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger().CreateChildLogger(""))); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); @@ -96,7 +96,7 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle() { - var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger())); + var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger().CreateChildLogger(""))); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapterFactory.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapterFactory.cs index 4fe35373..6873c69e 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapterFactory.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapterFactory.cs @@ -13,7 +13,7 @@ namespace MQTTnet.Core.Tests _adapter = adapter; } - public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetLogger logger) + public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetChildLogger logger) { return _adapter; } From dc7b9414d9080922ac77a32bd9317408ae6502b5 Mon Sep 17 00:00:00 2001 From: Christian Date: Sun, 13 May 2018 20:18:08 +0200 Subject: [PATCH 043/138] Refactor logging. Remove TCP adapter from HTTP host if disabled. --- Build/MQTTnet.nuspec | 1 + .../ApplicationBuilderExtensions.cs | 5 +++++ .../MqttWebSocketServerAdapter.cs | 4 ++-- .../ServiceCollectionExtensions.cs | 16 ++++++++++++---- .../MQTTnet.NetStandard/Client/MqttClient.cs | 12 ++++-------- .../Diagnostics/IMqttNetChildLogger.cs | 2 +- .../Diagnostics/IMqttNetLogger.cs | 2 +- .../Diagnostics/MqttNetLogger.cs | 2 +- .../Implementations/MqttTcpServerAdapter.cs | 15 +++++---------- Frameworks/MQTTnet.NetStandard/MqttFactory.cs | 8 ++++---- .../MQTTnet.NetStandard/Server/MqttServer.cs | 6 ++---- .../MQTTnet.TestApp.AspNetCore2.csproj | 2 +- Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs | 4 ++-- 13 files changed, 41 insertions(+), 38 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 5f965a3e..8f496adb 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -11,6 +11,7 @@ false MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). * [Core] Performance optimizations. +* [Core] Due to performance reasons the timestamp of log messages is now in UTC format. * [Client] Received messages are now processed in the worker thread by default. Added a new setting for switching back to dedicated threads. * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot). * [Server] The takeover of an existing client sessions is now treated as a _clean_ disconnect of the previous client. diff --git a/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs index 87b2038b..e2497f14 100644 --- a/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs +++ b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs @@ -25,6 +25,11 @@ namespace MQTTnet.AspNetCore .FirstOrDefault(p => p.ToLower().StartsWith("mqtt")); } + if (subprotocol == null) + { + return; + } + var adapter = app.ApplicationServices.GetRequiredService(); using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subprotocol)) { diff --git a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index 666c71ef..d5401dca 100644 --- a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -31,12 +31,12 @@ namespace MQTTnet.AspNetCore var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); ClientAccepted?.Invoke(this, eventArgs); - return eventArgs.SessionTask; + return eventArgs.SessionTask ?? Task.CompletedTask; } public void Dispose() { - StopAsync(); + StopAsync().GetAwaiter().GetResult(); } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs index 61354cd9..5b8f1612 100644 --- a/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs +++ b/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs @@ -14,16 +14,24 @@ namespace MQTTnet.AspNetCore { if (options == null) throw new ArgumentNullException(nameof(options)); + var logger = new MqttNetLogger(); + var childLogger = logger.CreateChildLogger(); + services.AddSingleton(options); - services.AddSingleton(new MqttNetLogger()); + services.AddSingleton(logger); + services.AddSingleton(childLogger); services.AddSingleton(); services.AddSingleton(s => s.GetService()); services.AddSingleton(s => s.GetService()); - + services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(s => s.GetService()); - services.AddSingleton(s => s.GetService()); + services.AddSingleton(s => s.GetService()); + + if (options.DefaultEndpointOptions.IsEnabled) + { + services.AddSingleton(s => s.GetService()); + } return services; } diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index b342ddc2..0a781bc5 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -504,20 +504,16 @@ namespace MQTTnet.Client private void StartReceivingPackets(CancellationToken cancellationToken) { - _packetReceiverTask = Task.Factory.StartNew( + _packetReceiverTask = Task.Run( () => ReceivePacketsAsync(cancellationToken), - cancellationToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Current); + cancellationToken); } private void StartSendingKeepAliveMessages(CancellationToken cancellationToken) { - _keepAliveMessageSenderTask = Task.Factory.StartNew( + _keepAliveMessageSenderTask = Task.Run( () => SendKeepAliveMessagesAsync(cancellationToken), - cancellationToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Current); + cancellationToken); } private void StartProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetChildLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetChildLogger.cs index 55d99e5d..df99f443 100644 --- a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetChildLogger.cs +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetChildLogger.cs @@ -4,7 +4,7 @@ namespace MQTTnet.Diagnostics { public interface IMqttNetChildLogger { - IMqttNetChildLogger CreateChildLogger(string source); + IMqttNetChildLogger CreateChildLogger(string source = null); void Verbose(string message, params object[] parameters); diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs index d2aacf40..73b6fcbd 100644 --- a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs @@ -6,7 +6,7 @@ namespace MQTTnet.Diagnostics { event EventHandler LogMessagePublished; - IMqttNetChildLogger CreateChildLogger(string source); + IMqttNetChildLogger CreateChildLogger(string source = null); void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception); } diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs index 868e5d7c..6afeba19 100644 --- a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs @@ -13,7 +13,7 @@ namespace MQTTnet.Diagnostics public event EventHandler LogMessagePublished; - public IMqttNetChildLogger CreateChildLogger(string source) + public IMqttNetChildLogger CreateChildLogger(string source = null) { return new MqttNetChildLogger(this, source); } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs index 85c15cec..40f6b2ef 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs @@ -45,11 +45,8 @@ namespace MQTTnet.Implementations _defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort())); _defaultEndpointSocket.Listen(options.ConnectionBacklog); - Task.Factory.StartNew( - () => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token), - _cancellationTokenSource.Token, - TaskCreationOptions.LongRunning, - TaskScheduler.Current); + Task.Run(() => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token), + _cancellationTokenSource.Token); } if (options.TlsEndpointOptions.IsEnabled) @@ -69,11 +66,9 @@ namespace MQTTnet.Implementations _tlsEndpointSocket.Bind(new IPEndPoint(options.TlsEndpointOptions.BoundIPAddress, options.GetTlsEndpointPort())); _tlsEndpointSocket.Listen(options.ConnectionBacklog); - Task.Factory.StartNew( - () => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), - _cancellationTokenSource.Token, - TaskCreationOptions.LongRunning, - TaskScheduler.Current); + Task.Run( + () => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), + _cancellationTokenSource.Token); } return Task.FromResult(0); diff --git a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs index d9aa9443..0ba3d2f0 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs @@ -25,14 +25,14 @@ namespace MQTTnet public IManagedMqttClient CreateManagedMqttClient() { - return new ManagedMqttClient(CreateMqttClient(), new MqttNetLogger().CreateChildLogger(string.Empty)); + return new ManagedMqttClient(CreateMqttClient(), new MqttNetLogger().CreateChildLogger()); } public IManagedMqttClient CreateManagedMqttClient(IMqttNetLogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); - return new ManagedMqttClient(CreateMqttClient(), logger.CreateChildLogger(string.Empty)); + return new ManagedMqttClient(CreateMqttClient(), logger.CreateChildLogger()); } public IMqttServer CreateMqttServer() @@ -45,7 +45,7 @@ namespace MQTTnet { if (logger == null) throw new ArgumentNullException(nameof(logger)); - return CreateMqttServer(new List { new MqttTcpServerAdapter(logger.CreateChildLogger(string.Empty)) }, logger); + return CreateMqttServer(new List { new MqttTcpServerAdapter(logger.CreateChildLogger()) }, logger); } public IMqttServer CreateMqttServer(IEnumerable adapters, IMqttNetLogger logger) @@ -53,7 +53,7 @@ namespace MQTTnet if (adapters == null) throw new ArgumentNullException(nameof(adapters)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - return new MqttServer(adapters, logger.CreateChildLogger(string.Empty)); + return new MqttServer(adapters, logger.CreateChildLogger()); } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs index a10d19c4..4323e2e3 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs @@ -157,11 +157,9 @@ namespace MQTTnet.Server private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) { - eventArgs.SessionTask = Task.Factory.StartNew( + eventArgs.SessionTask = Task.Run( () => _clientSessionsManager.RunSessionAsync(eventArgs.Client, _cancellationTokenSource.Token), - _cancellationTokenSource.Token, - TaskCreationOptions.LongRunning, - TaskScheduler.Current); + _cancellationTokenSource.Token); } } } diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj index 29b72538..cd38d16c 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj @@ -10,7 +10,7 @@ - + diff --git a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index 878a7884..27d8077b 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -17,8 +17,8 @@ namespace MQTTnet.TestApp.NetCore Console.WriteLine("Press 'c' for concurrent sends. Otherwise in one batch."); var concurrent = Console.ReadKey(true).KeyChar == 'c'; - var server = Task.Factory.StartNew(async () => await RunServerAsync(), TaskCreationOptions.LongRunning); - var client = Task.Factory.StartNew(async () => await RunClientAsync(2000, TimeSpan.FromMilliseconds(10), concurrent), TaskCreationOptions.LongRunning); + var server = Task.Run(RunServerAsync); + var client = Task.Run(() => RunClientAsync(2000, TimeSpan.FromMilliseconds(10), concurrent)); await Task.WhenAll(server, client).ConfigureAwait(false); } From 3e6edb92c5de5de55b0e904970535f5a18e5a579 Mon Sep 17 00:00:00 2001 From: Christian Date: Sun, 13 May 2018 20:59:00 +0200 Subject: [PATCH 044/138] Refactor ASP.NET integration. --- .../ApplicationBuilderExtensions.cs | 42 ++++++++----------- .../MqttWebSocketServerAdapter.cs | 8 +++- .../MQTTnet.NetStandard/Server/MqttServer.cs | 8 ++-- .../wwwroot/app/app.ts | 8 ++-- .../MQTTnet.TestApp.UniversalWindows.csproj | 2 +- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs index e2497f14..513712e5 100644 --- a/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs +++ b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs @@ -13,32 +13,26 @@ namespace MQTTnet.AspNetCore app.UseWebSockets(); app.Use(async (context, next) => { - if (context.Request.Path == path && context.WebSockets.IsWebSocketRequest) - { - string subprotocol = null; - - if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues)) - { - // Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc. - subprotocol = requestedSubProtocolValues - .OrderByDescending(p => p.Length) - .FirstOrDefault(p => p.ToLower().StartsWith("mqtt")); - } - - if (subprotocol == null) - { - return; - } - - var adapter = app.ApplicationServices.GetRequiredService(); - using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subprotocol)) - { - await adapter.AcceptWebSocketAsync(webSocket); - } - } - else + if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != path) { await next(); + return; + } + + string subProtocol = null; + + if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues)) + { + // Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc. + subProtocol = requestedSubProtocolValues + .OrderByDescending(p => p.Length) + .FirstOrDefault(p => p.ToLower().StartsWith("mqtt")); + } + + var adapter = app.ApplicationServices.GetRequiredService(); + using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol)) + { + await adapter.AcceptWebSocketAsync(webSocket); } }); diff --git a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index d5401dca..a10417ff 100644 --- a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -23,7 +23,7 @@ namespace MQTTnet.AspNetCore return Task.CompletedTask; } - public Task AcceptWebSocketAsync(WebSocket webSocket) + public async Task AcceptWebSocketAsync(WebSocket webSocket) { if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); @@ -31,7 +31,11 @@ namespace MQTTnet.AspNetCore var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); ClientAccepted?.Invoke(this, eventArgs); - return eventArgs.SessionTask ?? Task.CompletedTask; + + if (eventArgs.SessionTask != null) + { + await eventArgs.SessionTask.ConfigureAwait(false); + } } public void Dispose() diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs index 4323e2e3..f7f506dc 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs @@ -81,14 +81,14 @@ namespace MQTTnet.Server _cancellationTokenSource = new CancellationTokenSource(); _retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger); - await _retainedMessagesManager.LoadMessagesAsync(); + await _retainedMessagesManager.LoadMessagesAsync().ConfigureAwait(false); _clientSessionsManager = new MqttClientSessionsManager(Options, this, _retainedMessagesManager, _logger); foreach (var adapter in _adapters) { adapter.ClientAccepted += OnClientAccepted; - await adapter.StartAsync(Options); + await adapter.StartAsync(Options).ConfigureAwait(false); } _logger.Info("Started."); @@ -110,10 +110,10 @@ namespace MQTTnet.Server foreach (var adapter in _adapters) { adapter.ClientAccepted -= OnClientAccepted; - await adapter.StopAsync(); + await adapter.StopAsync().ConfigureAwait(false); } - await _clientSessionsManager.StopAsync(); + await _clientSessionsManager.StopAsync().ConfigureAwait(false); _logger.Info("Stopped."); } diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts index 3ce98566..b7167701 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts +++ b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts @@ -2,7 +2,7 @@ var client = connect('ws://' + location.host + '/mqtt', { - clientId: "client1", + clientId: "client" + Math.floor(Math.random() * 6) + 1 }); window.onbeforeunload = () => { @@ -23,11 +23,12 @@ publishButton.onclick = click => { client.on('connect', () => { client.subscribe('#', { qos: 0 }, (err, granted) => { - console.log(err); + console.log(err); }); client.publish('presence', 'Hello mqtt'); stateParagraph.innerText = "connected"; + showMsg("[connect]"); }); client.on("error", e => { @@ -36,6 +37,7 @@ client.on("error", e => { client.on("reconnect", () => { stateParagraph.innerText = "reconnecting"; + showMsg("[reconnect]"); }); client.on('message', (topic, message) => { @@ -50,7 +52,7 @@ function showMsg(msg: string) { msgsList.appendChild(node); - if (msgsList.childElementCount > 20) { + if (msgsList.childElementCount > 50) { msgsList.removeChild(msgsList.childNodes[0]); } } \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj index 73636f04..0a69b342 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj @@ -138,7 +138,7 @@ - 6.0.8 + 6.1.4 2.2.0 From 10740f12c4e98bb22bb0a053050ae0e3f2e4afe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Warin?= Date: Tue, 15 May 2018 15:28:14 +0200 Subject: [PATCH 045/138] Replace the Async coordination primitives --- .../Internal/AsyncAutoResetEvent.cs | 101 ++++++++- .../MQTTnet.NetStandard/Internal/AsyncLock.cs | 25 ++- .../Server/MqttClientPendingMessagesQueue.cs | 1 - .../Server/MqttClientSessionsManager.cs | 44 +--- .../Server/MqttRetainedMessagesManager.cs | 56 +++-- .../AsyncAutoResentEventTests.cs | 192 +++++++++++++++++- Tests/MQTTnet.Core.Tests/AsyncLockTests.cs | 9 +- 7 files changed, 325 insertions(+), 103 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs b/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs index ad20646c..0a304e65 100644 --- a/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs @@ -1,26 +1,109 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace MQTTnet.Internal { - public sealed class AsyncAutoResetEvent : IDisposable + // Inspired from Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-2-asyncautoresetevent/) and Chris Gillum (https://stackoverflow.com/a/43012490) + public class AsyncAutoResetEvent { - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0, 1); + private readonly LinkedList> waiters = new LinkedList>(); - public Task WaitOneAsync(CancellationToken cancellationToken) + private bool isSignaled; + + public AsyncAutoResetEvent(bool signaled = false) { - return _semaphore.WaitAsync(cancellationToken); + this.isSignaled = signaled; + } + + public Task WaitOneAsync() + { + return this.WaitOneAsync(CancellationToken.None); + } + + public Task WaitOneAsync(TimeSpan timeout) + { + return this.WaitOneAsync(timeout, CancellationToken.None); + } + + public Task WaitOneAsync(CancellationToken cancellationToken) + { + return this.WaitOneAsync(Timeout.InfiniteTimeSpan, cancellationToken); + } + + public async Task WaitOneAsync(TimeSpan timeout, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + TaskCompletionSource tcs; + + lock (this.waiters) + { + if (this.isSignaled) + { + this.isSignaled = false; + return true; + } + else if (timeout == TimeSpan.Zero) + { + return this.isSignaled; + } + else + { + tcs = new TaskCompletionSource(); + this.waiters.AddLast(tcs); + } + } + + Task winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)); + if (winner == tcs.Task) + { + // 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. + lock (this.waiters) + { + bool removed = this.waiters.Remove(tcs); + if (winner.Status == TaskStatus.Canceled) + { + throw new OperationCanceledException(cancellationToken); + } + else + { + throw new TimeoutException(); + } + } + } } public void Set() { - _semaphore.Release(); - } + TaskCompletionSource toRelease = null; - public void Dispose() - { - _semaphore?.Dispose(); + lock (this.waiters) + { + if (this.waiters.Count > 0) + { + // Signal the first task in the waiters list. + toRelease = this.waiters.First.Value; + this.waiters.RemoveFirst(); + } + else if (!this.isSignaled) + { + // No tasks are pending + this.isSignaled = true; + } + } + + if (toRelease != null) + { + toRelease.SetResult(true); + } } } } diff --git a/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs b/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs index 145e385f..d4029493 100644 --- a/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs @@ -4,23 +4,32 @@ using System.Threading.Tasks; namespace MQTTnet.Internal { - public sealed class AsyncLock : IDisposable + // From Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-6-asynclock/) + public sealed class AsyncLock { - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim m_semaphore = new SemaphoreSlim(1, 1); + private readonly Task m_releaser; - public Task EnterAsync(CancellationToken cancellationToken) + public AsyncLock() { - return _semaphore.WaitAsync(cancellationToken); + m_releaser = Task.FromResult((IDisposable)new Releaser(this)); } - public void Exit() + public Task LockAsync(CancellationToken cancellationToken) { - _semaphore.Release(); + var wait = m_semaphore.WaitAsync(cancellationToken); + return wait.IsCompleted ? + m_releaser : + wait.ContinueWith((_, state) => (IDisposable)state, + m_releaser.Result, cancellationToken, + TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } - public void Dispose() + private sealed class Releaser : IDisposable { - _semaphore?.Dispose(); + private readonly AsyncLock m_toRelease; + internal Releaser(AsyncLock toRelease) { m_toRelease = toRelease; } + public void Dispose() { m_toRelease.m_semaphore.Release(); } } } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs index fc74d4c5..f08e18e3 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs @@ -154,7 +154,6 @@ namespace MQTTnet.Server public void Dispose() { - _queueAutoResetEvent?.Dispose(); } } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index e50b8c24..e47f3f32 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -119,8 +119,7 @@ namespace MQTTnet.Server public async Task StopAsync() { - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { foreach (var session in _sessions) { @@ -129,16 +128,11 @@ namespace MQTTnet.Server _sessions.Clear(); } - finally - { - _sessionsLock.Exit(); - } } public async Task> GetConnectedClientsAsync() { - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient { @@ -149,10 +143,6 @@ namespace MQTTnet.Server PendingApplicationMessages = s.Value.PendingMessagesQueue.Count }).ToList(); } - finally - { - _sessionsLock.Exit(); - } } public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) @@ -165,8 +155,7 @@ namespace MQTTnet.Server if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { if (!_sessions.TryGetValue(clientId, out var session)) { @@ -175,10 +164,6 @@ namespace MQTTnet.Server await session.SubscribeAsync(topicFilters).ConfigureAwait(false); } - finally - { - _sessionsLock.Exit(); - } } public async Task UnsubscribeAsync(string clientId, IList topicFilters) @@ -186,8 +171,7 @@ namespace MQTTnet.Server if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { if (!_sessions.TryGetValue(clientId, out var session)) { @@ -196,15 +180,10 @@ namespace MQTTnet.Server await session.UnsubscribeAsync(topicFilters).ConfigureAwait(false); } - finally - { - _sessionsLock.Exit(); - } } public void Dispose() { - _sessionsLock?.Dispose(); } private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) @@ -226,8 +205,8 @@ namespace MQTTnet.Server private async Task PrepareClientSessionAsync(MqttConnectPacket connectPacket) { - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + { var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); if (isSessionPresent) @@ -261,10 +240,6 @@ namespace MQTTnet.Server return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession }; } - finally - { - _sessionsLock.Exit(); - } } private async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) @@ -294,18 +269,13 @@ namespace MQTTnet.Server _logger.Error(exception, "Error while processing application message"); } - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { foreach (var clientSession in _sessions.Values) { await clientSession.EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false); } } - finally - { - _sessionsLock.Exit(); - } } private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs index be0a5b7b..e00ff444 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs @@ -29,24 +29,22 @@ namespace MQTTnet.Server return; } - await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + using (var releaser = await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { - var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync().ConfigureAwait(false); - - _messages.Clear(); - foreach (var retainedMessage in retainedMessages) + try { - _messages[retainedMessage.Topic] = retainedMessage; + var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync().ConfigureAwait(false); + + _messages.Clear(); + foreach (var retainedMessage in retainedMessages) + { + _messages[retainedMessage.Topic] = retainedMessage; + } + } + catch (Exception exception) + { + _logger.Error(exception, "Unhandled exception while loading retained messages."); } - } - catch (Exception exception) - { - _logger.Error(exception, "Unhandled exception while loading retained messages."); - } - finally - { - _messagesLock.Exit(); } } @@ -54,18 +52,16 @@ namespace MQTTnet.Server { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + using (var releaser = await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { - await HandleMessageInternalAsync(clientId, applicationMessage); - } - catch (Exception exception) - { - _logger.Error(exception, "Unhandled exception while handling retained messages."); - } - finally - { - _messagesLock.Exit(); + try + { + await HandleMessageInternalAsync(clientId, applicationMessage); + } + catch (Exception exception) + { + _logger.Error(exception, "Unhandled exception while handling retained messages."); + } } } @@ -73,8 +69,7 @@ namespace MQTTnet.Server { var retainedMessages = new List(); - await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + using (var releaser = await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { foreach (var retainedMessage in _messages.Values) { @@ -90,17 +85,12 @@ namespace MQTTnet.Server } } } - finally - { - _messagesLock.Exit(); - } return retainedMessages; } public void Dispose() { - _messagesLock?.Dispose(); } private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage) diff --git a/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs b/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs index 77275eb9..879f9256 100644 --- a/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs +++ b/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs @@ -1,34 +1,210 @@ -using System.Threading; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; +using System; +using System.Threading; +using System.Threading.Tasks; namespace MQTTnet.Core.Tests { [TestClass] + // Inspired from the vs-threading tests (https://github.com/Microsoft/vs-threading/blob/master/src/Microsoft.VisualStudio.Threading.Tests/AsyncAutoResetEventTests.cs) public class AsyncAutoResetEventTests { + private AsyncAutoResetEvent evt; + + public AsyncAutoResetEventTests() + { + this.evt = new AsyncAutoResetEvent(); + } + + [TestMethod] + public async Task SingleThreadedPulse() + { + for (int i = 0; i < 5; i++) + { + var t = this.evt.WaitOneAsync(); + Assert.IsFalse(t.IsCompleted); + this.evt.Set(); + await t; + Assert.IsTrue(t.IsCompleted); + } + } + + [TestMethod] + public async Task MultipleSetOnlySignalsOnce() + { + this.evt.Set(); + this.evt.Set(); + await this.evt.WaitOneAsync(); + var t = this.evt.WaitOneAsync(); + Assert.IsFalse(t.IsCompleted); + await Task.Delay(500); + Assert.IsFalse(t.IsCompleted); + this.evt.Set(); + await t; + Assert.IsTrue(t.IsCompleted); + } + + [TestMethod] + public async Task OrderPreservingQueue() + { + var waiters = new Task[5]; + for (int i = 0; i < waiters.Length; i++) + { + waiters[i] = this.evt.WaitOneAsync(); + } + + for (int i = 0; i < waiters.Length; i++) + { + this.evt.Set(); + await waiters[i]; + } + } + + /// + /// Verifies that inlining continuations do not have to complete execution before Set() returns. + /// + [TestMethod] + public async Task SetReturnsBeforeInlinedContinuations() + { + var setReturned = new ManualResetEventSlim(); + var inlinedContinuation = this.evt.WaitOneAsync() + .ContinueWith(delegate + { + // Arrange to synchronously block the continuation until Set() has returned, + // which would deadlock if Set does not return until inlined continuations complete. + Assert.IsTrue(setReturned.Wait(500)); + }); + await Task.Delay(100); + this.evt.Set(); + setReturned.Set(); + Assert.IsTrue(inlinedContinuation.Wait(500)); + } + + [TestMethod] + public void WaitAsync_WithCancellationToken() + { + var cts = new CancellationTokenSource(); + Task waitTask = this.evt.WaitOneAsync(cts.Token); + Assert.IsFalse(waitTask.IsCompleted); + + // Cancel the request and ensure that it propagates to the task. + cts.Cancel(); + try + { + waitTask.GetAwaiter().GetResult(); + Assert.IsTrue(false, "Task was expected to transition to a canceled state."); + } + catch (System.OperationCanceledException ex) + { + Assert.AreEqual(cts.Token, ex.CancellationToken); + } + + // Now set the event and verify that a future waiter gets the signal immediately. + this.evt.Set(); + waitTask = this.evt.WaitOneAsync(); + Assert.AreEqual(TaskStatus.RanToCompletion, waitTask.Status); + } + + [TestMethod] + public void WaitAsync_WithCancellationToken_Precanceled() + { + // We construct our own pre-canceled token so that we can do + // a meaningful identity check later. + var tokenSource = new CancellationTokenSource(); + tokenSource.Cancel(); + var token = tokenSource.Token; + + // Verify that a pre-set signal is not reset by a canceled wait request. + this.evt.Set(); + try + { + this.evt.WaitOneAsync(token).GetAwaiter().GetResult(); + Assert.IsTrue(false, "Task was expected to transition to a canceled state."); + } + catch (OperationCanceledException ex) + { + Assert.AreEqual(token, ex.CancellationToken); + } + + // Verify that the signal was not acquired. + Task waitTask = this.evt.WaitOneAsync(); + Assert.AreEqual(TaskStatus.RanToCompletion, waitTask.Status); + } + + [TestMethod] + public async Task WaitAsync_WithTimeout() + { + Task waitTask = this.evt.WaitOneAsync(TimeSpan.FromMilliseconds(500)); + Assert.IsFalse(waitTask.IsCompleted); + + // Cancel the request and ensure that it propagates to the task. + await Task.Delay(1000); + try + { + waitTask.GetAwaiter().GetResult(); + Assert.IsTrue(false, "Task was expected to transition to a timeout state."); + } + catch (System.TimeoutException) + { + Assert.IsTrue(true); + } + + // Now set the event and verify that a future waiter gets the signal immediately. + this.evt.Set(); + waitTask = this.evt.WaitOneAsync(TimeSpan.FromMilliseconds(500)); + Assert.AreEqual(TaskStatus.RanToCompletion, waitTask.Status); + } + + [TestMethod] + public void WaitAsync_Canceled_DoesNotInlineContinuations() + { + var cts = new CancellationTokenSource(); + var task = this.evt.WaitOneAsync(cts.Token); + + var completingActionFinished = new ManualResetEventSlim(); + var continuation = task.ContinueWith( + _ => Assert.IsTrue(completingActionFinished.Wait(500)), + CancellationToken.None, + TaskContinuationOptions.None, + TaskScheduler.Default); + + cts.Cancel(); + completingActionFinished.Set(); + + // Rethrow the exception if it turned out it deadlocked. + continuation.GetAwaiter().GetResult(); + } + [TestMethod] public async Task AsyncAutoResetEvent() { var aare = new AsyncAutoResetEvent(); - var increment = 0; var globalI = 0; #pragma warning disable 4014 Task.Run(async () => #pragma warning restore 4014 { await aare.WaitOneAsync(CancellationToken.None); - globalI += increment; + globalI += 1; + }); + +#pragma warning disable 4014 + Task.Run(async () => +#pragma warning restore 4014 + { + await aare.WaitOneAsync(CancellationToken.None); + globalI += 2; }); await Task.Delay(500); - increment = 1; + aare.Set(); + await Task.Delay(500); aare.Set(); await Task.Delay(100); - Assert.AreEqual(1, globalI); + Assert.AreEqual(3, globalI); } } -} +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs b/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs index 56c70500..43f2486c 100644 --- a/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs +++ b/Tests/MQTTnet.Core.Tests/AsyncLockTests.cs @@ -21,22 +21,17 @@ namespace MQTTnet.Core.Tests #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed threads[i] = Task.Run(async () => { - await @lock.EnterAsync(CancellationToken.None); - try + using (var releaser = await @lock.LockAsync(CancellationToken.None)) { var localI = globalI; await Task.Delay(10); // Increase the chance for wrong data. localI++; globalI = localI; } - finally - { - @lock.Exit(); - } }); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed } - + Task.WaitAll(threads); Assert.AreEqual(ThreadsCount, globalI); } From 5287bf5cac2f832b39baa8580b736ebddd8d17d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Warin?= Date: Tue, 15 May 2018 16:54:37 +0200 Subject: [PATCH 046/138] Codacy/PR Quality Review --- .../MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs | 9 ++++++--- Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs | 7 ++++++- Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs | 6 +++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs b/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs index 0a304e65..a8fa8529 100644 --- a/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs @@ -12,7 +12,10 @@ namespace MQTTnet.Internal private bool isSignaled; - public AsyncAutoResetEvent(bool signaled = false) + public AsyncAutoResetEvent() : this(false) + { } + + public AsyncAutoResetEvent(bool signaled) { this.isSignaled = signaled; } @@ -56,7 +59,7 @@ namespace MQTTnet.Internal } } - Task winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)); + Task winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).ConfigureAwait(false); if (winner == tcs.Task) { // The task was signaled. @@ -68,7 +71,7 @@ namespace MQTTnet.Internal // This is an O(n) operation since waiters is a LinkedList. lock (this.waiters) { - bool removed = this.waiters.Remove(tcs); + this.waiters.Remove(tcs); if (winner.Status == TaskStatus.Canceled) { throw new OperationCanceledException(cancellationToken); diff --git a/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs b/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs index d4029493..5e60a6a9 100644 --- a/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace MQTTnet.Internal { // From Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-6-asynclock/) - public sealed class AsyncLock + public sealed class AsyncLock : IDisposable { private readonly SemaphoreSlim m_semaphore = new SemaphoreSlim(1, 1); private readonly Task m_releaser; @@ -25,6 +25,11 @@ namespace MQTTnet.Internal TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } + public void Dispose() + { + this.m_semaphore?.Dispose(); + } + private sealed class Releaser : IDisposable { private readonly AsyncLock m_toRelease; diff --git a/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs b/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs index 879f9256..a4909b4a 100644 --- a/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs +++ b/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs @@ -10,7 +10,7 @@ namespace MQTTnet.Core.Tests // Inspired from the vs-threading tests (https://github.com/Microsoft/vs-threading/blob/master/src/Microsoft.VisualStudio.Threading.Tests/AsyncAutoResetEventTests.cs) public class AsyncAutoResetEventTests { - private AsyncAutoResetEvent evt; + private readonly AsyncAutoResetEvent evt; public AsyncAutoResetEventTests() { @@ -57,7 +57,7 @@ namespace MQTTnet.Core.Tests for (int i = 0; i < waiters.Length; i++) { this.evt.Set(); - await waiters[i]; + await waiters[i].ConfigureAwait(false); } } @@ -139,7 +139,7 @@ namespace MQTTnet.Core.Tests Assert.IsFalse(waitTask.IsCompleted); // Cancel the request and ensure that it propagates to the task. - await Task.Delay(1000); + await Task.Delay(1000).ConfigureAwait(false); try { waitTask.GetAwaiter().GetResult(); From ba66c108fa78dd019b3fc2777807a23e14d8573c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Warin?= Date: Tue, 15 May 2018 17:39:45 +0200 Subject: [PATCH 047/138] Fixes and clean the new topic matcher from israellot --- .../Server/MqttTopicFilterComparer.cs | 128 ++++++++++++++---- .../MQTTnet.Benchmarks.csproj | 1 + Tests/MQTTnet.Benchmarks/Program.cs | 4 + .../TopicFilterComparerBenchmark.cs | 91 +++++++++++++ 4 files changed, 195 insertions(+), 29 deletions(-) create mode 100644 Tests/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs index 00e0bd65..c02be734 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs @@ -4,46 +4,116 @@ namespace MQTTnet.Server { public static class MqttTopicFilterComparer { - private static readonly char[] TopicLevelSeparator = { '/' }; + private const char LEVEL_SEPARATOR = '/'; + private const char WILDCARD_MULTI_LEVEL = '#'; + private const char WILDCARD_SINGLE_LEVEL = '+'; public static bool IsMatch(string topic, string filter) { - if (topic == null) throw new ArgumentNullException(nameof(topic)); - if (filter == null) throw new ArgumentNullException(nameof(filter)); + if (string.IsNullOrEmpty(topic)) throw new ArgumentNullException(nameof(topic)); + if (string.IsNullOrEmpty(filter)) throw new ArgumentNullException(nameof(filter)); - if (string.Equals(topic, filter, StringComparison.Ordinal)) + int spos = 0; + int slen = filter.Length; + int tpos = 0; + int tlen = topic.Length; + + while (spos < slen && tpos < tlen) { - return true; - } - - var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None); - var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None); - - // # > In either case it MUST be the last character specified in the Topic Filter [MQTT-4.7.1-2]. - for (var i = 0; i < fragmentsFilter.Length; i++) - { - if (fragmentsFilter[i] == "+") + if (filter[spos] == topic[tpos]) { - continue; + if (tpos == tlen - 1) + { + /* Check for e.g. foo matching foo/# */ + if (spos == slen - 3 + && filter[spos + 1] == LEVEL_SEPARATOR + && filter[spos + 2] == WILDCARD_MULTI_LEVEL) + { + return true; + } + } + spos++; + tpos++; + if (spos == slen && tpos == tlen) + { + return true; + } + else if (tpos == tlen && spos == slen - 1 && filter[spos] == WILDCARD_SINGLE_LEVEL) + { + if (spos > 0 && filter[spos - 1] != LEVEL_SEPARATOR) + { + // Invalid filter string + return false; + } + spos++; + return true; + } } - - if (fragmentsFilter[i] == "#") + else { - return true; - } - - if (i >= fragmentsTopic.Length) - { - return false; - } - - if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i], StringComparison.Ordinal)) - { - return false; + if (filter[spos] == WILDCARD_SINGLE_LEVEL) + { + /* Check for bad "+foo" or "a/+foo" subscription */ + if (spos > 0 && filter[spos - 1] != LEVEL_SEPARATOR) + { + // Invalid filter string + return false; + } + /* Check for bad "foo+" or "foo+/a" subscription */ + if (spos < slen - 1 && filter[spos + 1] != LEVEL_SEPARATOR) + { + // Invalid filter string + return false; + } + spos++; + while (tpos < tlen && topic[tpos] != LEVEL_SEPARATOR) + { + tpos++; + } + if (tpos == tlen && spos == slen) + { + return true; + } + } + else if (filter[spos] == WILDCARD_MULTI_LEVEL) + { + if (spos > 0 && filter[spos - 1] != LEVEL_SEPARATOR) + { + // Invalid filter string + return false; + } + if (spos + 1 != slen) + { + // Invalid filter string + return false; + } + else + { + return true; + } + } + else + { + /* Check for e.g. foo/bar matching foo/+/# */ + if (spos > 0 + && spos + 2 == slen + && tpos == tlen + && filter[spos - 1] == WILDCARD_SINGLE_LEVEL + && filter[spos] == LEVEL_SEPARATOR + && filter[spos + 1] == WILDCARD_MULTI_LEVEL) + { + return true; + } + return false; + } } } + if (tpos < tlen || spos < slen) + { + return false; + } - return fragmentsTopic.Length == fragmentsFilter.Length; + return false; } } } diff --git a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index e068fa9d..8d3a7c89 100644 --- a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -147,6 +147,7 @@ + diff --git a/Tests/MQTTnet.Benchmarks/Program.cs b/Tests/MQTTnet.Benchmarks/Program.cs index 363cf8f2..a65de421 100644 --- a/Tests/MQTTnet.Benchmarks/Program.cs +++ b/Tests/MQTTnet.Benchmarks/Program.cs @@ -12,6 +12,7 @@ namespace MQTTnet.Benchmarks Console.WriteLine("1 = MessageProcessingBenchmark"); Console.WriteLine("2 = SerializerBenchmark"); Console.WriteLine("3 = LoggerBenchmark"); + Console.WriteLine("4 = TopicFilterComparerBenchmark"); var pressedKey = Console.ReadKey(true); switch (pressedKey.KeyChar) @@ -25,6 +26,9 @@ namespace MQTTnet.Benchmarks case '3': BenchmarkRunner.Run(); break; + case '4': + BenchmarkRunner.Run(); + break; } Console.ReadLine(); diff --git a/Tests/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs b/Tests/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs new file mode 100644 index 00000000..6d9a0321 --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs @@ -0,0 +1,91 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Exporters; +using BenchmarkDotNet.Attributes.Jobs; +using MQTTnet.Server; +using System; + +namespace MQTTnet.Benchmarks +{ + [ClrJob] + [RPlotExporter] + [MemoryDiagnoser] + public class TopicFilterComparerBenchmark + { + private static readonly char[] TopicLevelSeparator = { '/' }; + + [GlobalSetup] + public void Setup() + { + } + + [Benchmark] + public void MqttTopicFilterComparer_10000_StringSplitMethod() + { + for (var i = 0; i < 10000; i++) + { + LegacyMethodByStringSplit("sport/tennis/player1", "sport/#"); + LegacyMethodByStringSplit("sport/tennis/player1/ranking", "sport/#/ranking"); + LegacyMethodByStringSplit("sport/tennis/player1/score/wimbledon", "sport/+/player1/#"); + LegacyMethodByStringSplit("sport/tennis/player1", "sport/tennis/+"); + LegacyMethodByStringSplit("/finance", "+/+"); + LegacyMethodByStringSplit("/finance", "/+"); + LegacyMethodByStringSplit("/finance", "+"); + } + } + + [Benchmark] + public void MqttTopicFilterComparer_10000_LoopMethod() + { + for (var i = 0; i < 10000; i++) + { + MqttTopicFilterComparer.IsMatch("sport/tennis/player1", "sport/#"); + MqttTopicFilterComparer.IsMatch("sport/tennis/player1/ranking", "sport/#/ranking"); + MqttTopicFilterComparer.IsMatch("sport/tennis/player1/score/wimbledon", "sport/+/player1/#"); + MqttTopicFilterComparer.IsMatch("sport/tennis/player1", "sport/tennis/+"); + MqttTopicFilterComparer.IsMatch("/finance", "+/+"); + MqttTopicFilterComparer.IsMatch("/finance", "/+"); + MqttTopicFilterComparer.IsMatch("/finance", "+"); + } + } + + private static bool LegacyMethodByStringSplit(string topic, string filter) + { + if (topic == null) throw new ArgumentNullException(nameof(topic)); + if (filter == null) throw new ArgumentNullException(nameof(filter)); + + if (string.Equals(topic, filter, StringComparison.Ordinal)) + { + return true; + } + + var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None); + var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None); + + // # > In either case it MUST be the last character specified in the Topic Filter [MQTT-4.7.1-2]. + for (var i = 0; i < fragmentsFilter.Length; i++) + { + if (fragmentsFilter[i] == "+") + { + continue; + } + + if (fragmentsFilter[i] == "#") + { + return true; + } + + if (i >= fragmentsTopic.Length) + { + return false; + } + + if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i], StringComparison.Ordinal)) + { + return false; + } + } + + return fragmentsTopic.Length == fragmentsFilter.Length; + } + } +} From 719838de3be0fa4b442df6d51813065a4a4d8e25 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 15 May 2018 22:04:05 +0200 Subject: [PATCH 048/138] Add new server option to disable/enable persistent sessions. --- .../Server/IMqttServerOptions.cs | 3 +++ .../Server/MqttClientSessionsManager.cs | 19 +++++++++++++++++++ .../Server/MqttServerOptions.cs | 3 ++- .../Server/MqttServerOptionsBuilder.cs | 6 ++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs index 36373192..54e037d8 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs @@ -5,6 +5,9 @@ namespace MQTTnet.Server public interface IMqttServerOptions { int ConnectionBacklog { get; } + + bool EnablePersistentSessions { get; } + int MaxPendingMessagesPerClient { get; } MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index e50b8c24..267c8e87 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -107,6 +107,11 @@ namespace MQTTnet.Server _logger.Error(exception, exception.Message); } + if (!_options.EnablePersistentSessions) + { + await DeleteSessionAsync(clientId).ConfigureAwait(false); + } + Server.OnClientDisconnected(new ConnectedMqttClient { ClientId = clientId, @@ -224,6 +229,20 @@ namespace MQTTnet.Server return context.ReturnCode; } + private async Task DeleteSessionAsync(string clientId) + { + await _sessionsLock.EnterAsync(CancellationToken.None); + try + { + _sessions.Remove(clientId); + _logger.Verbose("Session for client '{0}' deleted.", clientId); + } + finally + { + _sessionsLock.Exit(); + } + } + private async Task PrepareClientSessionAsync(MqttConnectPacket connectPacket) { await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs index e6117fde..e8485554 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs @@ -8,10 +8,11 @@ namespace MQTTnet.Server public MqttServerTlsEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsEndpointOptions(); + public bool EnablePersistentSessions { get; set; } + public int ConnectionBacklog { get; set; } = 10; public int MaxPendingMessagesPerClient { get; set; } = 250; - public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage; public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15); diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs index 2c865125..a8800d09 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs @@ -103,6 +103,12 @@ namespace MQTTnet.Server return this; } + public MqttServerOptionsBuilder WithPersistentSessions() + { + _options.EnablePersistentSessions = true; + return this; + } + public IMqttServerOptions Build() { return _options; From fbf1cbc76e1ceaa09d8d4483ccb35203a0129b21 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 16 May 2018 00:08:42 +0200 Subject: [PATCH 049/138] Refactor ConnectedClient statistics API --- .../ApplicationBuilderExtensions.cs | 3 +- .../MqttWebSocketServerAdapter.cs | 4 +- .../Adapter/IMqttChannelAdapter.cs | 4 +- .../Adapter/MqttChannelAdapter.cs | 2 + .../Channel/IMqttChannel.cs | 2 + .../Implementations/MqttTcpChannel.Uwp.cs | 2 + .../Implementations/MqttTcpChannel.cs | 2 + .../Implementations/MqttWebSocketChannel.cs | 7 +- .../Internal/TestMqttChannel.cs | 2 + .../Server/ConnectedMqttClient.cs | 22 --- .../Server/IMqttClientSessionStatus.cs | 29 +++ .../MQTTnet.NetStandard/Server/IMqttServer.cs | 2 +- .../Server/MqttClientConnectedEventArgs.cs | 6 +- .../Server/MqttClientDisconnectedEventArgs.cs | 6 +- .../Server/MqttClientKeepAliveMonitor.cs | 26 ++- .../Server/MqttClientPendingMessagesQueue.cs | 28 ++- .../Server/MqttClientSession.cs | 171 ++++++++---------- .../Server/MqttClientSessionStatus.cs | 53 ++++++ .../Server/MqttClientSessionsManager.cs | 152 +++++----------- .../Server/MqttClientSubscriptionsManager.cs | 44 ++--- .../Server/MqttRetainedMessagesManager.cs | 56 ++---- .../MQTTnet.NetStandard/Server/MqttServer.cs | 19 +- .../MqttSubscriptionsManagerTests.cs | 12 +- .../TestMqttCommunicationAdapter.cs | 2 + 24 files changed, 306 insertions(+), 350 deletions(-) delete mode 100644 Frameworks/MQTTnet.NetStandard/Server/ConnectedMqttClient.cs create mode 100644 Frameworks/MQTTnet.NetStandard/Server/IMqttClientSessionStatus.cs create mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionStatus.cs diff --git a/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs index 513712e5..22b9e937 100644 --- a/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs +++ b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs @@ -32,7 +32,8 @@ namespace MQTTnet.AspNetCore var adapter = app.ApplicationServices.GetRequiredService(); using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol)) { - await adapter.AcceptWebSocketAsync(webSocket); + var endpoint = $"{context.Connection.RemoteIpAddress}:{context.Connection.RemotePort}"; + await adapter.RunWebSocketConnectionAsync(webSocket, endpoint); } }); diff --git a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index a10417ff..dbd93604 100644 --- a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -23,11 +23,11 @@ namespace MQTTnet.AspNetCore return Task.CompletedTask; } - public async Task AcceptWebSocketAsync(WebSocket webSocket) + public async Task RunWebSocketConnectionAsync(WebSocket webSocket, string endpoint) { if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); - var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket), new MqttPacketSerializer(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter))); + var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket, endpoint), new MqttPacketSerializer(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter))); var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); ClientAccepted?.Invoke(this, eventArgs); diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs index 0970affc..a08d9f67 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs @@ -9,8 +9,10 @@ namespace MQTTnet.Adapter { public interface IMqttChannelAdapter : IDisposable { - IMqttPacketSerializer PacketSerializer { get; } + string Endpoint { get; } + IMqttPacketSerializer PacketSerializer { get; } + Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken); Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken); diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 9af60315..ee2f586c 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -33,6 +33,8 @@ namespace MQTTnet.Adapter _logger = logger.CreateChildLogger(nameof(MqttChannelAdapter)); } + public string Endpoint => _channel.Endpoint; + public IMqttPacketSerializer PacketSerializer { get; } public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken) diff --git a/Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs b/Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs index dd9d1f27..6050bcb1 100644 --- a/Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs @@ -6,6 +6,8 @@ namespace MQTTnet.Channel { public interface IMqttChannel : IDisposable { + string Endpoint { get; } + Task ConnectAsync(CancellationToken cancellationToken); Task DisconnectAsync(); diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs index 19d58240..15f8566e 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs @@ -43,6 +43,8 @@ namespace MQTTnet.Implementations public static Func> CustomIgnorableServerCertificateErrorsResolver { get; set; } + public string Endpoint => _socket?.Information?.RemoteAddress?.ToString(); // TODO: Check if contains also the port. + public async Task ConnectAsync(CancellationToken cancellationToken) { if (_socket == null) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs index fe017810..40edf367 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs @@ -41,6 +41,8 @@ namespace MQTTnet.Implementations public static Func CustomCertificateValidationCallback { get; set; } + public string Endpoint => _socket?.RemoteEndPoint?.ToString(); + public async Task ConnectAsync(CancellationToken cancellationToken) { if (_socket == null) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs index 5ceecbba..733bcdd1 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs @@ -14,17 +14,20 @@ namespace MQTTnet.Implementations private readonly MqttClientWebSocketOptions _options; private WebSocket _webSocket; - + public MqttWebSocketChannel(MqttClientWebSocketOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); } - public MqttWebSocketChannel(WebSocket webSocket) + public MqttWebSocketChannel(WebSocket webSocket, string endpoint) { _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); + Endpoint = endpoint; } + public string Endpoint { get; } + public async Task ConnectAsync(CancellationToken cancellationToken) { var uri = _options.Uri; diff --git a/Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs b/Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs index b380b085..21d0584b 100644 --- a/Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs @@ -18,6 +18,8 @@ namespace MQTTnet.Core.Internal { } + public string Endpoint { get; } + public Task ConnectAsync(CancellationToken cancellationToken) { return Task.FromResult(0); diff --git a/Frameworks/MQTTnet.NetStandard/Server/ConnectedMqttClient.cs b/Frameworks/MQTTnet.NetStandard/Server/ConnectedMqttClient.cs deleted file mode 100644 index c12759a0..00000000 --- a/Frameworks/MQTTnet.NetStandard/Server/ConnectedMqttClient.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using MQTTnet.Serializer; - -namespace MQTTnet.Server -{ - // TODO: Rename to "RegisteredClient" - // TODO: Add IsConnected - // TODO: Add interface - - public class ConnectedMqttClient - { - public string ClientId { get; set; } - - public MqttProtocolVersion? ProtocolVersion { get; set; } - - public TimeSpan LastPacketReceived { get; set; } - - public TimeSpan LastNonKeepAlivePacketReceived { get; set; } - - public int PendingApplicationMessages { get; set; } - } -} diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttClientSessionStatus.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttClientSessionStatus.cs new file mode 100644 index 00000000..d9c55938 --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttClientSessionStatus.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading.Tasks; +using MQTTnet.Serializer; + +namespace MQTTnet.Server +{ + public interface IMqttClientSessionStatus + { + string ClientId { get; } + + string Endpoint { get; } + + bool IsConnected { get; } + + MqttProtocolVersion? ProtocolVersion { get; } + + TimeSpan LastPacketReceived { get; } + + TimeSpan LastNonKeepAlivePacketReceived { get; } + + int PendingApplicationMessagesCount { get; } + + Task DisconnectAsync(); + + Task DeleteSessionAsync(); + + Task ClearPendingApplicationMessagesAsync(); + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs index 229e79c5..b0fce015 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs @@ -15,7 +15,7 @@ namespace MQTTnet.Server IMqttServerOptions Options { get; } - Task> GetConnectedClientsAsync(); + Task> GetClientSessionsStatusAsync(); Task SubscribeAsync(string clientId, IList topicFilters); Task UnsubscribeAsync(string clientId, IList topicFilters); diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientConnectedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientConnectedEventArgs.cs index 1ca3f406..6f043b44 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientConnectedEventArgs.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientConnectedEventArgs.cs @@ -4,11 +4,11 @@ namespace MQTTnet.Server { public class MqttClientConnectedEventArgs : EventArgs { - public MqttClientConnectedEventArgs(ConnectedMqttClient client) + public MqttClientConnectedEventArgs(string clientId) { - Client = client ?? throw new ArgumentNullException(nameof(client)); + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); } - public ConnectedMqttClient Client { get; } + public string ClientId { get; } } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientDisconnectedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientDisconnectedEventArgs.cs index 6dd505e5..7e88548e 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientDisconnectedEventArgs.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientDisconnectedEventArgs.cs @@ -4,13 +4,13 @@ namespace MQTTnet.Server { public class MqttClientDisconnectedEventArgs : EventArgs { - public MqttClientDisconnectedEventArgs(ConnectedMqttClient client, bool wasCleanDisconnect) + public MqttClientDisconnectedEventArgs(string clientId, bool wasCleanDisconnect) { - Client = client ?? throw new ArgumentNullException(nameof(client)); + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); WasCleanDisconnect = wasCleanDisconnect; } - public ConnectedMqttClient Client { get; } + public string ClientId { get; } public bool WasCleanDisconnect { get; } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs index 46196008..d73b183c 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs @@ -12,18 +12,18 @@ namespace MQTTnet.Server private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch(); private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch(); - private readonly string _clientId; - private readonly Action _timeoutCallback; private readonly IMqttNetChildLogger _logger; + private readonly string _clientId; + private readonly Action _callback; private Task _workerTask; - public MqttClientKeepAliveMonitor(string clientId, Action timeoutCallback, IMqttNetChildLogger logger) + public MqttClientKeepAliveMonitor(string clientId, Action callback, IMqttNetChildLogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); _clientId = clientId; - _timeoutCallback = timeoutCallback; + _callback = callback; _logger = logger.CreateChildLogger(nameof(MqttClientKeepAliveMonitor)); } @@ -38,16 +38,13 @@ namespace MQTTnet.Server return; } - _workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false), cancellationToken); + _workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken), cancellationToken); } - public void WaitForCompletion() - { - if (_workerTask != null) - { - Task.WaitAll(_workerTask); - } - } + ////public void WaitForCompletion() + ////{ + //// SpinWait.SpinUntil(() => _workerTask == null || _workerTask.IsCanceled || _workerTask.IsCompleted || _workerTask.IsFaulted); + ////} private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken) { @@ -62,9 +59,8 @@ namespace MQTTnet.Server if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D) { _logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientId); - - _timeoutCallback?.Invoke(); - + _callback(); + return; } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs index fc74d4c5..619ff703 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs @@ -13,12 +13,12 @@ namespace MQTTnet.Server { public sealed class MqttClientPendingMessagesQueue : IDisposable { - private readonly ConcurrentQueue _queue = new ConcurrentQueue(); private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent(); private readonly IMqttServerOptions _options; private readonly MqttClientSession _clientSession; private readonly IMqttNetChildLogger _logger; + private ConcurrentQueue _queue = new ConcurrentQueue(); private Task _workerTask; public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger) @@ -43,15 +43,7 @@ namespace MQTTnet.Server _workerTask = Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken); } - - public void WaitForCompletion() - { - if (_workerTask != null) - { - Task.WaitAll(_workerTask); - } - } - + public void Enqueue(MqttBasePacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); @@ -75,6 +67,17 @@ namespace MQTTnet.Server _logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId); } + public void Clear() + { + var newQueue = new ConcurrentQueue(); + Interlocked.Exchange(ref _queue, newQueue); + } + + public void Dispose() + { + _queueAutoResetEvent?.Dispose(); + } + private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) { try @@ -151,10 +154,5 @@ namespace MQTTnet.Server } } } - - public void Dispose() - { - _queueAutoResetEvent?.Dispose(); - } } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs index 1d0b77d4..c42a3007 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs @@ -9,21 +9,26 @@ using MQTTnet.Exceptions; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -using MQTTnet.Serializer; namespace MQTTnet.Server { public sealed class MqttClientSession : IDisposable { private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); + private readonly MqttRetainedMessagesManager _retainedMessagesManager; + private readonly MqttClientKeepAliveMonitor _keepAliveMonitor; + private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue; + private readonly MqttClientSubscriptionsManager _subscriptionsManager; + private readonly MqttClientSessionsManager _sessionsManager; + private readonly IMqttNetChildLogger _logger; private readonly IMqttServerOptions _options; - private readonly MqttClientSessionsManager _sessionsManager; private CancellationTokenSource _cancellationTokenSource; private MqttApplicationMessage _willMessage; private bool _wasCleanDisconnect; + private IMqttChannelAdapter _adapter; public MqttClientSession( string clientId, @@ -37,27 +42,28 @@ namespace MQTTnet.Server _options = options ?? throw new ArgumentNullException(nameof(options)); _sessionsManager = sessionsManager; _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); - + ClientId = clientId; _logger = logger.CreateChildLogger(nameof(MqttClientSession)); - KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeout, _logger); - SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server); - PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); + _keepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, () => Stop(MqttClientDisconnectType.NotClean), _logger); + _subscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server); + _pendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); } - public MqttClientSubscriptionsManager SubscriptionsManager { get; } - - public MqttClientPendingMessagesQueue PendingMessagesQueue { get; } - - public MqttClientKeepAliveMonitor KeepAliveMonitor { get; } - public string ClientId { get; } - public MqttProtocolVersion? ProtocolVersion { get; private set; } - - public bool IsConnected { get; private set; } + public void FillStatus(MqttClientSessionStatus status) + { + status.ClientId = ClientId; + status.IsConnected = _adapter != null; + status.Endpoint = _adapter?.Endpoint; + status.ProtocolVersion = _adapter?.PacketSerializer?.ProtocolVersion; + status.PendingApplicationMessagesCount = _pendingMessagesQueue.Count; + status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived; + status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; + } public async Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) { @@ -66,34 +72,41 @@ namespace MQTTnet.Server try { - _cancellationTokenSource = new CancellationTokenSource(); + _adapter = adapter; + _cancellationTokenSource = new CancellationTokenSource(); _wasCleanDisconnect = false; _willMessage = connectPacket.WillMessage; - IsConnected = true; - ProtocolVersion = adapter.PacketSerializer.ProtocolVersion; + _pendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token); + _keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token); - PendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token); - KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token); - - await ReceivePacketsAsync(adapter, _cancellationTokenSource.Token).ConfigureAwait(false); + while (!_cancellationTokenSource.IsCancellationRequested) + { + var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false); + _keepAliveMonitor.PacketReceived(packet); + await ProcessReceivedPacketAsync(adapter, packet, _cancellationTokenSource.Token).ConfigureAwait(false); + } } catch (OperationCanceledException) { } - catch (MqttCommunicationException exception) - { - _logger.Warning(exception, "Client '{0}': Communication exception while processing client packets.", ClientId); - } catch (Exception exception) { - _logger.Error(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); + if (exception is MqttCommunicationException) + { + _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId); + } + else + { + _logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId); + } + + Stop(MqttClientDisconnectType.NotClean); } finally { - ProtocolVersion = null; - IsConnected = false; + _adapter = null; _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; @@ -106,7 +119,8 @@ namespace MQTTnet.Server { try { - if (_cancellationTokenSource == null) + var cts = _cancellationTokenSource; + if (cts == null || cts.IsCancellationRequested) { return; } @@ -114,16 +128,16 @@ namespace MQTTnet.Server _wasCleanDisconnect = type == MqttClientDisconnectType.Clean; _cancellationTokenSource?.Cancel(false); - PendingMessagesQueue.WaitForCompletion(); - KeepAliveMonitor.WaitForCompletion(); - var willMessage = _willMessage; - _willMessage = null; // clear willmessage so it is send just once - - if (willMessage != null && !_wasCleanDisconnect) + if (_willMessage != null && !_wasCleanDisconnect) { - _sessionsManager.StartDispatchApplicationMessage(this, willMessage); + _sessionsManager.StartDispatchApplicationMessage(this, _willMessage); } + + _willMessage = null; + + ////_pendingMessagesQueue.WaitForCompletion(); + ////_keepAliveMonitor.WaitForCompletion(); } finally { @@ -131,11 +145,11 @@ namespace MQTTnet.Server } } - public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage) + public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage).ConfigureAwait(false); + var result = _subscriptionsManager.CheckSubscriptions(applicationMessage); if (!result.IsSubscribed) { return; @@ -149,26 +163,27 @@ namespace MQTTnet.Server publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); } - PendingMessagesQueue.Enqueue(publishPacket); + _pendingMessagesQueue.Enqueue(publishPacket); } - public async Task SubscribeAsync(IList topicFilters) + public Task SubscribeAsync(IList topicFilters) { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - SubscriptionsManager.Subscribe(new MqttSubscribePacket + _subscriptionsManager.Subscribe(new MqttSubscribePacket { TopicFilters = topicFilters }); - await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false); + EnqueueSubscribedRetainedMessages(topicFilters); + return Task.FromResult(0); } public Task UnsubscribeAsync(IList topicFilters) { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - SubscriptionsManager.Unsubscribe(new MqttUnsubscribePacket + _subscriptionsManager.Unsubscribe(new MqttUnsubscribePacket { TopicFilters = topicFilters }); @@ -176,49 +191,18 @@ namespace MQTTnet.Server return Task.FromResult(0); } + public void ClearPendingApplicationMessages() + { + _pendingMessagesQueue.Clear(); + } + public void Dispose() { - SubscriptionsManager?.Dispose(); - PendingMessagesQueue?.Dispose(); + _pendingMessagesQueue?.Dispose(); _cancellationTokenSource?.Dispose(); } - private void StopDueToKeepAliveTimeout() - { - _logger.Info("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId); - Stop(MqttClientDisconnectType.NotClean); - } - - private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) - { - try - { - while (!cancellationToken.IsCancellationRequested) - { - var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); - KeepAliveMonitor.PacketReceived(packet); - await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false); - } - } - catch (OperationCanceledException) - { - } - catch (Exception exception) - { - if (exception is MqttCommunicationException) - { - _logger.Warning(exception, "Client '{0}': Communication exception while processing client packets.", ClientId); - } - else - { - _logger.Error(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); - } - - Stop(MqttClientDisconnectType.NotClean); - } - } - private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken) { if (packet is MqttPublishPacket publishPacket) @@ -276,22 +260,21 @@ namespace MQTTnet.Server _logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); Stop(MqttClientDisconnectType.NotClean); - return Task.FromResult(0); } - private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters) + private void EnqueueSubscribedRetainedMessages(ICollection topicFilters) { - var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters); + var retainedMessages = _retainedMessagesManager.GetSubscribedMessages(topicFilters); foreach (var applicationMessage in retainedMessages) { - await EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false); + EnqueueApplicationMessage(applicationMessage); } } private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) { - var subscribeResult = SubscriptionsManager.Subscribe(subscribePacket); + var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { subscribeResult.ResponsePacket }, cancellationToken).ConfigureAwait(false); if (subscribeResult.CloseConnection) @@ -300,13 +283,13 @@ namespace MQTTnet.Server return; } - await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false); + EnqueueSubscribedRetainedMessages(subscribePacket.TopicFilters); } - private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) + private Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) { - var unsubscribeResult = SubscriptionsManager.Unsubscribe(unsubscribePacket); - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { unsubscribeResult }, cancellationToken); + var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket); + return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { unsubscribeResult }, cancellationToken); } private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) @@ -335,21 +318,21 @@ namespace MQTTnet.Server } } - private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) + private Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) { _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage); var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }; - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false); + return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken); } - private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) + private Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) { // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage); var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false); + return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken); } private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionStatus.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionStatus.cs new file mode 100644 index 00000000..4d83357e --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionStatus.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading.Tasks; +using MQTTnet.Serializer; + +namespace MQTTnet.Server +{ + public class MqttClientSessionStatus : IMqttClientSessionStatus + { + private readonly MqttClientSessionsManager _sessionsManager; + private readonly MqttClientSession _session; + + public MqttClientSessionStatus(MqttClientSessionsManager sessionsManager, MqttClientSession session) + { + _sessionsManager = sessionsManager; + _session = session; + } + + public string ClientId { get; set; } + public string Endpoint { get; set; } + public bool IsConnected { get; set; } + public MqttProtocolVersion? ProtocolVersion { get; set; } + public TimeSpan LastPacketReceived { get; set; } + public TimeSpan LastNonKeepAlivePacketReceived { get; set; } + public int PendingApplicationMessagesCount { get; set; } + + public Task DisconnectAsync() + { + _session.Stop(MqttClientDisconnectType.NotClean); + return Task.FromResult(0); + } + + public Task DeleteSessionAsync() + { + try + { + _session.Stop(MqttClientDisconnectType.NotClean); + } + finally + { + _sessionsManager.DeleteSession(ClientId); + } + + return Task.FromResult(0); + } + + public Task ClearPendingApplicationMessagesAsync() + { + _session.ClearPendingApplicationMessages(); + + return Task.FromResult(0); + } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index 267c8e87..5282dc74 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -1,6 +1,6 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; @@ -14,8 +14,8 @@ namespace MQTTnet.Server { public sealed class MqttClientSessionsManager : IDisposable { - private readonly Dictionary _sessions = new Dictionary(); - private readonly AsyncLock _sessionsLock = new AsyncLock(); + private readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); + private readonly AsyncLock _sessionPreparationLock = new AsyncLock(); private readonly MqttRetainedMessagesManager _retainedMessagesManager; private readonly IMqttServerOptions _options; @@ -38,7 +38,6 @@ namespace MQTTnet.Server { var clientId = string.Empty; var wasCleanDisconnect = false; - MqttClientSession clientSession = null; try { @@ -69,7 +68,7 @@ namespace MQTTnet.Server } var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false); - clientSession = result.Session; + var clientSession = result.Session; await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { @@ -80,11 +79,7 @@ namespace MQTTnet.Server } }, cancellationToken).ConfigureAwait(false); - Server.OnClientConnected(new ConnectedMqttClient - { - ClientId = clientId, - ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion - }); + Server.OnClientConnected(clientId); wasCleanDisconnect = await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); } @@ -109,55 +104,36 @@ namespace MQTTnet.Server if (!_options.EnablePersistentSessions) { - await DeleteSessionAsync(clientId).ConfigureAwait(false); + DeleteSession(clientId); } - Server.OnClientDisconnected(new ConnectedMqttClient - { - ClientId = clientId, - ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion, - PendingApplicationMessages = clientSession?.PendingMessagesQueue.Count ?? 0 - }, - wasCleanDisconnect); + Server.OnClientDisconnected(clientId, wasCleanDisconnect); } } - public async Task StopAsync() + public Task StopAsync() { - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + foreach (var session in _sessions) { - foreach (var session in _sessions) - { - session.Value.Stop(MqttClientDisconnectType.NotClean); - } + session.Value.Stop(MqttClientDisconnectType.NotClean); + } - _sessions.Clear(); - } - finally - { - _sessionsLock.Exit(); - } + _sessions.Clear(); + return Task.FromResult(0); } - public async Task> GetConnectedClientsAsync() + public Task> GetClientStatusAsync() { - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + var result = new List(); + foreach (var session in _sessions) { - return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient - { - ClientId = s.Value.ClientId, - ProtocolVersion = s.Value.ProtocolVersion, - LastPacketReceived = s.Value.KeepAliveMonitor.LastPacketReceived, - LastNonKeepAlivePacketReceived = s.Value.KeepAliveMonitor.LastNonKeepAlivePacketReceived, - PendingApplicationMessages = s.Value.PendingMessagesQueue.Count - }).ToList(); - } - finally - { - _sessionsLock.Exit(); + var status = new MqttClientSessionStatus(this, session.Value); + session.Value.FillStatus(status); + + result.Add(status); } + + return Task.FromResult((IList)result); } public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) @@ -165,51 +141,41 @@ namespace MQTTnet.Server Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage)); } - public async Task SubscribeAsync(string clientId, IList topicFilters) + public Task SubscribeAsync(string clientId, IList topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + if (!_sessions.TryGetValue(clientId, out var session)) { - if (!_sessions.TryGetValue(clientId, out var session)) - { - throw new InvalidOperationException($"Client session '{clientId}' is unknown."); - } + throw new InvalidOperationException($"Client session '{clientId}' is unknown."); + } - await session.SubscribeAsync(topicFilters).ConfigureAwait(false); - } - finally - { - _sessionsLock.Exit(); - } + return session.SubscribeAsync(topicFilters); } - public async Task UnsubscribeAsync(string clientId, IList topicFilters) + public Task UnsubscribeAsync(string clientId, IList topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + if (!_sessions.TryGetValue(clientId, out var session)) { - if (!_sessions.TryGetValue(clientId, out var session)) - { - throw new InvalidOperationException($"Client session '{clientId}' is unknown."); - } + throw new InvalidOperationException($"Client session '{clientId}' is unknown."); + } - await session.UnsubscribeAsync(topicFilters).ConfigureAwait(false); - } - finally - { - _sessionsLock.Exit(); - } + return session.UnsubscribeAsync(topicFilters); + } + + public void DeleteSession(string clientId) + { + _sessions.TryRemove(clientId, out _); + _logger.Verbose("Session for client '{0}' deleted.", clientId); } public void Dispose() { - _sessionsLock?.Dispose(); + _sessionPreparationLock?.Dispose(); } private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) @@ -229,23 +195,9 @@ namespace MQTTnet.Server return context.ReturnCode; } - private async Task DeleteSessionAsync(string clientId) - { - await _sessionsLock.EnterAsync(CancellationToken.None); - try - { - _sessions.Remove(clientId); - _logger.Verbose("Session for client '{0}' deleted.", clientId); - } - finally - { - _sessionsLock.Exit(); - } - } - private async Task PrepareClientSessionAsync(MqttConnectPacket connectPacket) { - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); + await _sessionPreparationLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); @@ -253,7 +205,7 @@ namespace MQTTnet.Server { if (connectPacket.CleanSession) { - _sessions.Remove(connectPacket.ClientId); + _sessions.TryRemove(connectPacket.ClientId, out _); clientSession.Stop(MqttClientDisconnectType.Clean); clientSession.Dispose(); @@ -282,7 +234,7 @@ namespace MQTTnet.Server } finally { - _sessionsLock.Exit(); + _sessionPreparationLock.Exit(); } } @@ -301,30 +253,22 @@ namespace MQTTnet.Server return; } + Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage); + if (applicationMessage.Retain) { await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false); } - Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage); + foreach (var clientSession in _sessions.Values) + { + clientSession.EnqueueApplicationMessage(applicationMessage); + } } catch (Exception exception) { _logger.Error(exception, "Error while processing application message"); } - - await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try - { - foreach (var clientSession in _sessions.Values) - { - await clientSession.EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false); - } - } - finally - { - _sessionsLock.Exit(); - } } private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs index dfb94638..6dfa98e3 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs @@ -2,17 +2,14 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using MQTTnet.Packets; using MQTTnet.Protocol; namespace MQTTnet.Server { - public sealed class MqttClientSubscriptionsManager : IDisposable + public sealed class MqttClientSubscriptionsManager { private readonly ConcurrentDictionary _subscriptions = new ConcurrentDictionary(); - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly IMqttServerOptions _options; private readonly MqttServer _server; private readonly string _clientId; @@ -81,43 +78,30 @@ namespace MQTTnet.Server }; } - public async Task CheckSubscriptionsAsync(MqttApplicationMessage applicationMessage) + public CheckSubscriptionsResult CheckSubscriptions(MqttApplicationMessage applicationMessage) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - await _semaphore.WaitAsync().ConfigureAwait(false); - try + var qosLevels = new HashSet(); + foreach (var subscription in _subscriptions) { - var qosLevels = new HashSet(); - foreach (var subscription in _subscriptions) + if (!MqttTopicFilterComparer.IsMatch(applicationMessage.Topic, subscription.Key)) { - if (!MqttTopicFilterComparer.IsMatch(applicationMessage.Topic, subscription.Key)) - { - continue; - } - - qosLevels.Add(subscription.Value); + continue; } - if (qosLevels.Count == 0) - { - return new CheckSubscriptionsResult - { - IsSubscribed = false - }; - } - - return CreateSubscriptionResult(applicationMessage, qosLevels); + qosLevels.Add(subscription.Value); } - finally + + if (qosLevels.Count == 0) { - _semaphore.Release(); + return new CheckSubscriptionsResult + { + IsSubscribed = false + }; } - } - public void Dispose() - { - _semaphore?.Dispose(); + return CreateSubscriptionResult(applicationMessage, qosLevels); } private static MqttSubscribeReturnCode ConvertToMaximumQoS(MqttQualityOfServiceLevel qualityOfServiceLevel) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs index be0a5b7b..f2ab9448 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs @@ -1,17 +1,15 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using MQTTnet.Diagnostics; -using MQTTnet.Internal; namespace MQTTnet.Server { - public sealed class MqttRetainedMessagesManager : IDisposable + public sealed class MqttRetainedMessagesManager { - private readonly Dictionary _messages = new Dictionary(); - private readonly AsyncLock _messagesLock = new AsyncLock(); + private readonly ConcurrentDictionary _messages = new ConcurrentDictionary(); private readonly IMqttNetChildLogger _logger; private readonly IMqttServerOptions _options; @@ -29,7 +27,6 @@ namespace MQTTnet.Server return; } - await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync().ConfigureAwait(false); @@ -44,84 +41,61 @@ namespace MQTTnet.Server { _logger.Error(exception, "Unhandled exception while loading retained messages."); } - finally - { - _messagesLock.Exit(); - } } public async Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); try { - await HandleMessageInternalAsync(clientId, applicationMessage); + await HandleMessageInternalAsync(clientId, applicationMessage).ConfigureAwait(false); } catch (Exception exception) { _logger.Error(exception, "Unhandled exception while handling retained messages."); } - finally - { - _messagesLock.Exit(); - } } - public async Task> GetSubscribedMessagesAsync(ICollection topicFilters) + public IList GetSubscribedMessages(ICollection topicFilters) { var retainedMessages = new List(); - await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false); - try + foreach (var retainedMessage in _messages.Values) { - foreach (var retainedMessage in _messages.Values) + foreach (var topicFilter in topicFilters) { - foreach (var topicFilter in topicFilters) + if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic)) { - if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic)) - { - continue; - } - - retainedMessages.Add(retainedMessage); - break; + continue; } + + retainedMessages.Add(retainedMessage); + break; } } - finally - { - _messagesLock.Exit(); - } return retainedMessages; } - public void Dispose() - { - _messagesLock?.Dispose(); - } - private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage) { var saveIsRequired = false; - if (applicationMessage.Payload?.Any() == false) + if (applicationMessage.Payload?.Length > 0) { - saveIsRequired = _messages.Remove(applicationMessage.Topic); + saveIsRequired = _messages.TryRemove(applicationMessage.Topic, out _); _logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); } else { - if (!_messages.ContainsKey(applicationMessage.Topic)) + if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage)) { _messages[applicationMessage.Topic] = applicationMessage; saveIsRequired = true; } else { - var existingMessage = _messages[applicationMessage.Topic]; if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0])) { _messages[applicationMessage.Topic] = applicationMessage; diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs index f7f506dc..8004afa2 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs @@ -37,9 +37,9 @@ namespace MQTTnet.Server public IMqttServerOptions Options { get; private set; } - public Task> GetConnectedClientsAsync() + public Task> GetClientSessionsStatusAsync() { - return _clientSessionsManager.GetConnectedClientsAsync(); + return _clientSessionsManager.GetClientStatusAsync(); } public Task SubscribeAsync(string clientId, IList topicFilters) @@ -120,24 +120,23 @@ namespace MQTTnet.Server finally { _clientSessionsManager?.Dispose(); - _retainedMessagesManager?.Dispose(); - + _cancellationTokenSource = null; _retainedMessagesManager = null; _clientSessionsManager = null; } } - internal void OnClientConnected(ConnectedMqttClient client) + internal void OnClientConnected(string clientId) { - _logger.Info("Client '{0}': Connected.", client.ClientId); - ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client)); + _logger.Info("Client '{0}': Connected.", clientId); + ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientId)); } - internal void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect) + internal void OnClientDisconnected(string clientId, bool wasCleanDisconnect) { - _logger.Info("Client '{0}': Disconnected (clean={1}).", client.ClientId, wasCleanDisconnect); - ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client, wasCleanDisconnect)); + _logger.Info("Client '{0}': Disconnected (clean={1}).", clientId, wasCleanDisconnect); + ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientId, wasCleanDisconnect)); } internal void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter) diff --git a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs index cf922a15..aa84dd3e 100644 --- a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs @@ -26,7 +26,7 @@ namespace MQTTnet.Core.Tests QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }; - var result = sm.CheckSubscriptionsAsync(pp).Result; + var result = sm.CheckSubscriptions(pp); Assert.IsTrue(result.IsSubscribed); Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); } @@ -47,7 +47,7 @@ namespace MQTTnet.Core.Tests QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }; - var result = sm.CheckSubscriptionsAsync(pp).Result; + var result = sm.CheckSubscriptions(pp); Assert.IsTrue(result.IsSubscribed); Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); } @@ -69,7 +69,7 @@ namespace MQTTnet.Core.Tests QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }; - var result = sm.CheckSubscriptionsAsync(pp).Result; + var result = sm.CheckSubscriptions(pp); Assert.IsTrue(result.IsSubscribed); Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce); } @@ -90,7 +90,7 @@ namespace MQTTnet.Core.Tests QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }; - Assert.IsFalse(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed); + Assert.IsFalse(sm.CheckSubscriptions(pp).IsSubscribed); } [TestMethod] @@ -109,13 +109,13 @@ namespace MQTTnet.Core.Tests QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }; - Assert.IsTrue(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed); + Assert.IsTrue(sm.CheckSubscriptions(pp).IsSubscribed); var up = new MqttUnsubscribePacket(); up.TopicFilters.Add("A/B/C"); sm.Unsubscribe(up); - Assert.IsFalse(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed); + Assert.IsFalse(sm.CheckSubscriptions(pp).IsSubscribed); } } } diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index 837221b1..3b2dd1fb 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -15,6 +15,8 @@ namespace MQTTnet.Core.Tests public TestMqttCommunicationAdapter Partner { get; set; } + public string Endpoint { get; } + public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer(); public void Dispose() From 2499d2919631df6908f3358261def52635764f44 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 16 May 2018 00:12:09 +0200 Subject: [PATCH 050/138] Refactor events. --- .../MQTTnet.NetStandard/Server/IMqttServer.cs | 3 ++- .../MQTTnet.NetStandard/Server/MqttServer.cs | 3 ++- .../Server/MqttServerEventDispatcher.cs | 14 ++++++++++++++ .../Server/MqttServerStartedEventArgs.cs | 8 -------- 4 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttServerEventDispatcher.cs delete mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttServerStartedEventArgs.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs index b0fce015..e724a65f 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs @@ -6,7 +6,8 @@ namespace MQTTnet.Server { public interface IMqttServer : IApplicationMessageReceiver, IApplicationMessagePublisher { - event EventHandler Started; + event EventHandler Started; + event EventHandler Stopped; event EventHandler ClientConnected; event EventHandler ClientDisconnected; diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs index 8004afa2..292c7ab9 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs @@ -26,7 +26,8 @@ namespace MQTTnet.Server _adapters = adapters.ToList(); } - public event EventHandler Started; + public event EventHandler Started; + public event EventHandler Stopped; public event EventHandler ClientConnected; public event EventHandler ClientDisconnected; diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerEventDispatcher.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerEventDispatcher.cs new file mode 100644 index 00000000..53d0e82d --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerEventDispatcher.cs @@ -0,0 +1,14 @@ +using System; + +namespace MQTTnet.Server +{ + public class MqttServerEventDispatcher + { + public event EventHandler ClientConnected; + public event EventHandler ClientDisconnected; + public event EventHandler ClientSubscribedTopic; + public event EventHandler ClientUnsubscribedTopic; + + public event EventHandler ApplicationMessageReceived; + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerStartedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerStartedEventArgs.cs deleted file mode 100644 index 08202994..00000000 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerStartedEventArgs.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace MQTTnet.Server -{ - public class MqttServerStartedEventArgs : EventArgs - { - } -} From c1352d50c1c9a4d615de12b11010ea7552d2a345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Warin?= Date: Wed, 16 May 2018 10:00:29 +0200 Subject: [PATCH 051/138] Review code style to match with the existing code --- .../Internal/AsyncAutoResetEvent.cs | 44 ++++++++-------- .../MQTTnet.NetStandard/Internal/AsyncLock.cs | 28 +++++++---- .../Server/MqttClientSessionsManager.cs | 13 +++-- .../Server/MqttRetainedMessagesManager.cs | 6 +-- .../AsyncAutoResentEventTests.cs | 50 +++++++++---------- 5 files changed, 72 insertions(+), 69 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs b/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs index a8fa8529..f20733d8 100644 --- a/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs @@ -8,31 +8,30 @@ namespace MQTTnet.Internal // Inspired from Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-2-asyncautoresetevent/) and Chris Gillum (https://stackoverflow.com/a/43012490) public class AsyncAutoResetEvent { - private readonly LinkedList> waiters = new LinkedList>(); - - private bool isSignaled; + private readonly LinkedList> _waiters = new LinkedList>(); + private bool _isSignaled; public AsyncAutoResetEvent() : this(false) { } public AsyncAutoResetEvent(bool signaled) { - this.isSignaled = signaled; + _isSignaled = signaled; } public Task WaitOneAsync() { - return this.WaitOneAsync(CancellationToken.None); + return WaitOneAsync(CancellationToken.None); } public Task WaitOneAsync(TimeSpan timeout) { - return this.WaitOneAsync(timeout, CancellationToken.None); + return WaitOneAsync(timeout, CancellationToken.None); } public Task WaitOneAsync(CancellationToken cancellationToken) { - return this.WaitOneAsync(Timeout.InfiniteTimeSpan, cancellationToken); + return WaitOneAsync(Timeout.InfiniteTimeSpan, cancellationToken); } public async Task WaitOneAsync(TimeSpan timeout, CancellationToken cancellationToken) @@ -41,21 +40,21 @@ namespace MQTTnet.Internal TaskCompletionSource tcs; - lock (this.waiters) + lock (_waiters) { - if (this.isSignaled) + if (_isSignaled) { - this.isSignaled = false; + _isSignaled = false; return true; } else if (timeout == TimeSpan.Zero) { - return this.isSignaled; + return _isSignaled; } else { tcs = new TaskCompletionSource(); - this.waiters.AddLast(tcs); + _waiters.AddLast(tcs); } } @@ -69,9 +68,9 @@ namespace MQTTnet.Internal { // We timed-out; remove our reference to the task. // This is an O(n) operation since waiters is a LinkedList. - lock (this.waiters) + lock (_waiters) { - this.waiters.Remove(tcs); + _waiters.Remove(tcs); if (winner.Status == TaskStatus.Canceled) { throw new OperationCanceledException(cancellationToken); @@ -88,25 +87,22 @@ namespace MQTTnet.Internal { TaskCompletionSource toRelease = null; - lock (this.waiters) + lock (_waiters) { - if (this.waiters.Count > 0) + if (_waiters.Count > 0) { // Signal the first task in the waiters list. - toRelease = this.waiters.First.Value; - this.waiters.RemoveFirst(); + toRelease = _waiters.First.Value; + _waiters.RemoveFirst(); } - else if (!this.isSignaled) + else if (!_isSignaled) { // No tasks are pending - this.isSignaled = true; + _isSignaled = true; } } - if (toRelease != null) - { - toRelease.SetResult(true); - } + toRelease?.SetResult(true); } } } diff --git a/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs b/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs index 5e60a6a9..1de1b1a9 100644 --- a/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs @@ -7,34 +7,42 @@ namespace MQTTnet.Internal // From Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-6-asynclock/) public sealed class AsyncLock : IDisposable { - private readonly SemaphoreSlim m_semaphore = new SemaphoreSlim(1, 1); - private readonly Task m_releaser; + private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); + private readonly Task _releaser; public AsyncLock() { - m_releaser = Task.FromResult((IDisposable)new Releaser(this)); + _releaser = Task.FromResult((IDisposable)new Releaser(this)); } public Task LockAsync(CancellationToken cancellationToken) { - var wait = m_semaphore.WaitAsync(cancellationToken); + Task wait = _semaphore.WaitAsync(cancellationToken); return wait.IsCompleted ? - m_releaser : + _releaser : wait.ContinueWith((_, state) => (IDisposable)state, - m_releaser.Result, cancellationToken, + _releaser.Result, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } public void Dispose() { - this.m_semaphore?.Dispose(); + _semaphore?.Dispose(); } private sealed class Releaser : IDisposable { - private readonly AsyncLock m_toRelease; - internal Releaser(AsyncLock toRelease) { m_toRelease = toRelease; } - public void Dispose() { m_toRelease.m_semaphore.Release(); } + private readonly AsyncLock _toRelease; + + internal Releaser(AsyncLock toRelease) + { + _toRelease = toRelease; + } + + public void Dispose() + { + _toRelease._semaphore.Release(); + } } } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index e47f3f32..9281a971 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -119,7 +119,7 @@ namespace MQTTnet.Server public async Task StopAsync() { - using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + using (await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { foreach (var session in _sessions) { @@ -132,7 +132,7 @@ namespace MQTTnet.Server public async Task> GetConnectedClientsAsync() { - using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + using (await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient { @@ -155,7 +155,7 @@ namespace MQTTnet.Server if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + using (await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { if (!_sessions.TryGetValue(clientId, out var session)) { @@ -171,7 +171,7 @@ namespace MQTTnet.Server if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + using (await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { if (!_sessions.TryGetValue(clientId, out var session)) { @@ -205,8 +205,7 @@ namespace MQTTnet.Server private async Task PrepareClientSessionAsync(MqttConnectPacket connectPacket) { - using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) - + using (await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); if (isSessionPresent) @@ -269,7 +268,7 @@ namespace MQTTnet.Server _logger.Error(exception, "Error while processing application message"); } - using (var releaser = await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + using (await _sessionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { foreach (var clientSession in _sessions.Values) { diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs index e00ff444..89765b70 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs @@ -29,7 +29,7 @@ namespace MQTTnet.Server return; } - using (var releaser = await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { try { @@ -52,7 +52,7 @@ namespace MQTTnet.Server { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - using (var releaser = await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { try { @@ -69,7 +69,7 @@ namespace MQTTnet.Server { var retainedMessages = new List(); - using (var releaser = await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { foreach (var retainedMessage in _messages.Values) { diff --git a/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs b/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs index a4909b4a..7087ff04 100644 --- a/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs +++ b/Tests/MQTTnet.Core.Tests/AsyncAutoResentEventTests.cs @@ -10,11 +10,11 @@ namespace MQTTnet.Core.Tests // Inspired from the vs-threading tests (https://github.com/Microsoft/vs-threading/blob/master/src/Microsoft.VisualStudio.Threading.Tests/AsyncAutoResetEventTests.cs) public class AsyncAutoResetEventTests { - private readonly AsyncAutoResetEvent evt; + private readonly AsyncAutoResetEvent _aare; public AsyncAutoResetEventTests() { - this.evt = new AsyncAutoResetEvent(); + _aare = new AsyncAutoResetEvent(); } [TestMethod] @@ -22,9 +22,9 @@ namespace MQTTnet.Core.Tests { for (int i = 0; i < 5; i++) { - var t = this.evt.WaitOneAsync(); + var t = _aare.WaitOneAsync(); Assert.IsFalse(t.IsCompleted); - this.evt.Set(); + _aare.Set(); await t; Assert.IsTrue(t.IsCompleted); } @@ -33,14 +33,14 @@ namespace MQTTnet.Core.Tests [TestMethod] public async Task MultipleSetOnlySignalsOnce() { - this.evt.Set(); - this.evt.Set(); - await this.evt.WaitOneAsync(); - var t = this.evt.WaitOneAsync(); + _aare.Set(); + _aare.Set(); + await _aare.WaitOneAsync(); + var t = _aare.WaitOneAsync(); Assert.IsFalse(t.IsCompleted); await Task.Delay(500); Assert.IsFalse(t.IsCompleted); - this.evt.Set(); + _aare.Set(); await t; Assert.IsTrue(t.IsCompleted); } @@ -51,12 +51,12 @@ namespace MQTTnet.Core.Tests var waiters = new Task[5]; for (int i = 0; i < waiters.Length; i++) { - waiters[i] = this.evt.WaitOneAsync(); + waiters[i] = _aare.WaitOneAsync(); } for (int i = 0; i < waiters.Length; i++) { - this.evt.Set(); + _aare.Set(); await waiters[i].ConfigureAwait(false); } } @@ -68,7 +68,7 @@ namespace MQTTnet.Core.Tests public async Task SetReturnsBeforeInlinedContinuations() { var setReturned = new ManualResetEventSlim(); - var inlinedContinuation = this.evt.WaitOneAsync() + var inlinedContinuation = _aare.WaitOneAsync() .ContinueWith(delegate { // Arrange to synchronously block the continuation until Set() has returned, @@ -76,7 +76,7 @@ namespace MQTTnet.Core.Tests Assert.IsTrue(setReturned.Wait(500)); }); await Task.Delay(100); - this.evt.Set(); + _aare.Set(); setReturned.Set(); Assert.IsTrue(inlinedContinuation.Wait(500)); } @@ -85,7 +85,7 @@ namespace MQTTnet.Core.Tests public void WaitAsync_WithCancellationToken() { var cts = new CancellationTokenSource(); - Task waitTask = this.evt.WaitOneAsync(cts.Token); + Task waitTask = _aare.WaitOneAsync(cts.Token); Assert.IsFalse(waitTask.IsCompleted); // Cancel the request and ensure that it propagates to the task. @@ -95,14 +95,14 @@ namespace MQTTnet.Core.Tests waitTask.GetAwaiter().GetResult(); Assert.IsTrue(false, "Task was expected to transition to a canceled state."); } - catch (System.OperationCanceledException ex) + catch (OperationCanceledException ex) { Assert.AreEqual(cts.Token, ex.CancellationToken); } // Now set the event and verify that a future waiter gets the signal immediately. - this.evt.Set(); - waitTask = this.evt.WaitOneAsync(); + _aare.Set(); + waitTask = _aare.WaitOneAsync(); Assert.AreEqual(TaskStatus.RanToCompletion, waitTask.Status); } @@ -116,10 +116,10 @@ namespace MQTTnet.Core.Tests var token = tokenSource.Token; // Verify that a pre-set signal is not reset by a canceled wait request. - this.evt.Set(); + _aare.Set(); try { - this.evt.WaitOneAsync(token).GetAwaiter().GetResult(); + _aare.WaitOneAsync(token).GetAwaiter().GetResult(); Assert.IsTrue(false, "Task was expected to transition to a canceled state."); } catch (OperationCanceledException ex) @@ -128,14 +128,14 @@ namespace MQTTnet.Core.Tests } // Verify that the signal was not acquired. - Task waitTask = this.evt.WaitOneAsync(); + Task waitTask = _aare.WaitOneAsync(); Assert.AreEqual(TaskStatus.RanToCompletion, waitTask.Status); } [TestMethod] public async Task WaitAsync_WithTimeout() { - Task waitTask = this.evt.WaitOneAsync(TimeSpan.FromMilliseconds(500)); + Task waitTask = _aare.WaitOneAsync(TimeSpan.FromMilliseconds(500)); Assert.IsFalse(waitTask.IsCompleted); // Cancel the request and ensure that it propagates to the task. @@ -145,14 +145,14 @@ namespace MQTTnet.Core.Tests waitTask.GetAwaiter().GetResult(); Assert.IsTrue(false, "Task was expected to transition to a timeout state."); } - catch (System.TimeoutException) + catch (TimeoutException) { Assert.IsTrue(true); } // Now set the event and verify that a future waiter gets the signal immediately. - this.evt.Set(); - waitTask = this.evt.WaitOneAsync(TimeSpan.FromMilliseconds(500)); + _aare.Set(); + waitTask = _aare.WaitOneAsync(TimeSpan.FromMilliseconds(500)); Assert.AreEqual(TaskStatus.RanToCompletion, waitTask.Status); } @@ -160,7 +160,7 @@ namespace MQTTnet.Core.Tests public void WaitAsync_Canceled_DoesNotInlineContinuations() { var cts = new CancellationTokenSource(); - var task = this.evt.WaitOneAsync(cts.Token); + var task = _aare.WaitOneAsync(cts.Token); var completingActionFinished = new ManualResetEventSlim(); var continuation = task.ContinueWith( From 932aa1fd39115dc1a605509d1b732ca33b2f839a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Warin?= Date: Wed, 16 May 2018 11:46:09 +0200 Subject: [PATCH 052/138] Fixes chkr1011's refactoing and pass all tests --- .../MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs | 6 ++++-- .../Server/MqttRetainedMessagesManager.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index b9ab0136..62a10ee1 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -136,9 +136,9 @@ namespace MQTTnet.Server return Task.FromResult((IList)result); } - public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) + public Task StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) { - Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage)); + return DispatchApplicationMessageAsync(senderClientSession, applicationMessage); } public Task SubscribeAsync(string clientId, IList topicFilters) @@ -237,6 +237,7 @@ namespace MQTTnet.Server { try { + _logger.Verbose($"DispatchApplicationMessageAsync"); var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage); if (interceptorContext.CloseConnection) { @@ -257,6 +258,7 @@ namespace MQTTnet.Server foreach (var clientSession in _sessions.Values) { + _logger.Verbose($"DispatchApplicationMessageAsync for {clientSession.ClientId} "); clientSession.EnqueueApplicationMessage(applicationMessage); } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs index f2ab9448..3ab53cbf 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs @@ -82,7 +82,7 @@ namespace MQTTnet.Server { var saveIsRequired = false; - if (applicationMessage.Payload?.Length > 0) + if (applicationMessage.Payload?.Length == 0) { saveIsRequired = _messages.TryRemove(applicationMessage.Topic, out _); _logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); From 5e0bc17341fdc9c3625c2ad31cedf0a0c12dd8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Warin?= Date: Wed, 16 May 2018 20:47:27 +0200 Subject: [PATCH 053/138] Removing the debug log messages --- .../MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index 62a10ee1..e798a513 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -237,7 +237,6 @@ namespace MQTTnet.Server { try { - _logger.Verbose($"DispatchApplicationMessageAsync"); var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage); if (interceptorContext.CloseConnection) { @@ -258,7 +257,6 @@ namespace MQTTnet.Server foreach (var clientSession in _sessions.Values) { - _logger.Verbose($"DispatchApplicationMessageAsync for {clientSession.ClientId} "); clientSession.EnqueueApplicationMessage(applicationMessage); } } From 8077d8dfeb1236449b93bd14f1222f8cca8590cd Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 16 May 2018 23:07:39 +0200 Subject: [PATCH 054/138] Refactor TopicFilterComparer --- .../Server/MqttClientSessionsManager.cs | 4 +- .../Server/MqttTopicFilterComparer.cs | 96 ++++++++++--------- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index e798a513..b9ab0136 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -136,9 +136,9 @@ namespace MQTTnet.Server return Task.FromResult((IList)result); } - public Task StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) + public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) { - return DispatchApplicationMessageAsync(senderClientSession, applicationMessage); + Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage)); } public Task SubscribeAsync(string clientId, IList topicFilters) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs index c02be734..0b0fe6b2 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs @@ -4,114 +4,116 @@ namespace MQTTnet.Server { public static class MqttTopicFilterComparer { - private const char LEVEL_SEPARATOR = '/'; - private const char WILDCARD_MULTI_LEVEL = '#'; - private const char WILDCARD_SINGLE_LEVEL = '+'; + private const char LevelSeparator = '/'; + private const char MultiLevelWildcard = '#'; + private const char SingleLevelWildcard = '+'; public static bool IsMatch(string topic, string filter) { if (string.IsNullOrEmpty(topic)) throw new ArgumentNullException(nameof(topic)); if (string.IsNullOrEmpty(filter)) throw new ArgumentNullException(nameof(filter)); - int spos = 0; - int slen = filter.Length; - int tpos = 0; - int tlen = topic.Length; + var sPos = 0; + var sLen = filter.Length; + var tPos = 0; + var tLen = topic.Length; - while (spos < slen && tpos < tlen) + while (sPos < sLen && tPos < tLen) { - if (filter[spos] == topic[tpos]) + if (filter[sPos] == topic[tPos]) { - if (tpos == tlen - 1) + if (tPos == tLen - 1) { - /* Check for e.g. foo matching foo/# */ - if (spos == slen - 3 - && filter[spos + 1] == LEVEL_SEPARATOR - && filter[spos + 2] == WILDCARD_MULTI_LEVEL) + // Check for e.g. foo matching foo/# + if (sPos == sLen - 3 + && filter[sPos + 1] == LevelSeparator + && filter[sPos + 2] == MultiLevelWildcard) { return true; } } - spos++; - tpos++; - if (spos == slen && tpos == tlen) + + sPos++; + tPos++; + + if (sPos == sLen && tPos == tLen) { return true; } - else if (tpos == tlen && spos == slen - 1 && filter[spos] == WILDCARD_SINGLE_LEVEL) + + if (tPos == tLen && sPos == sLen - 1 && filter[sPos] == SingleLevelWildcard) { - if (spos > 0 && filter[spos - 1] != LEVEL_SEPARATOR) + if (sPos > 0 && filter[sPos - 1] != LevelSeparator) { // Invalid filter string return false; } - spos++; + return true; } } else { - if (filter[spos] == WILDCARD_SINGLE_LEVEL) + if (filter[sPos] == SingleLevelWildcard) { - /* Check for bad "+foo" or "a/+foo" subscription */ - if (spos > 0 && filter[spos - 1] != LEVEL_SEPARATOR) + // Check for bad "+foo" or "a/+foo" subscription + if (sPos > 0 && filter[sPos - 1] != LevelSeparator) { // Invalid filter string return false; } - /* Check for bad "foo+" or "foo+/a" subscription */ - if (spos < slen - 1 && filter[spos + 1] != LEVEL_SEPARATOR) + + // Check for bad "foo+" or "foo+/a" subscription + if (sPos < sLen - 1 && filter[sPos + 1] != LevelSeparator) { // Invalid filter string return false; } - spos++; - while (tpos < tlen && topic[tpos] != LEVEL_SEPARATOR) + + sPos++; + while (tPos < tLen && topic[tPos] != LevelSeparator) { - tpos++; + tPos++; } - if (tpos == tlen && spos == slen) + + if (tPos == tLen && sPos == sLen) { return true; } } - else if (filter[spos] == WILDCARD_MULTI_LEVEL) + else if (filter[sPos] == MultiLevelWildcard) { - if (spos > 0 && filter[spos - 1] != LEVEL_SEPARATOR) + if (sPos > 0 && filter[sPos - 1] != LevelSeparator) { // Invalid filter string return false; } - if (spos + 1 != slen) + + if (sPos + 1 != sLen) { // Invalid filter string return false; } - else - { - return true; - } + + return true; } else { - /* Check for e.g. foo/bar matching foo/+/# */ - if (spos > 0 - && spos + 2 == slen - && tpos == tlen - && filter[spos - 1] == WILDCARD_SINGLE_LEVEL - && filter[spos] == LEVEL_SEPARATOR - && filter[spos + 1] == WILDCARD_MULTI_LEVEL) + // Check for e.g. foo/bar matching foo/+/# + if (sPos > 0 + && sPos + 2 == sLen + && tPos == tLen + && filter[sPos - 1] == SingleLevelWildcard + && filter[sPos] == LevelSeparator + && filter[sPos + 1] == MultiLevelWildcard) { return true; } + return false; } } } - if (tpos < tlen || spos < slen) - { - return false; - } return false; } From 79a2952f94559b4eca73b5c14338e8a4ce24406c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Warin?= Date: Thu, 17 May 2018 12:29:50 +0200 Subject: [PATCH 055/138] Adding delay between Pub&Sub in the test unit --- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 29 ++++++++++----------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index bcd9fb85..6fe27530 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -1,13 +1,13 @@ -using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Client; +using MQTTnet.Diagnostics; +using MQTTnet.Protocol; +using MQTTnet.Server; +using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Diagnostics; -using MQTTnet.Protocol; -using MQTTnet.Server; -using MQTTnet.Client; namespace MQTTnet.Core.Tests { @@ -96,10 +96,11 @@ namespace MQTTnet.Core.Tests c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); - + await c2.PublishAsync(message); + await Task.Delay(1000); Assert.AreEqual(0, receivedMessagesCount); - + var subscribeEventCalled = false; s.ClientSubscribedTopic += (_, e) => { @@ -107,14 +108,13 @@ namespace MQTTnet.Core.Tests }; await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce)); - await Task.Delay(100); + await Task.Delay(500); Assert.IsTrue(subscribeEventCalled, "Subscribe event not called."); await c2.PublishAsync(message); - await Task.Delay(500); Assert.AreEqual(1, receivedMessagesCount); - + var unsubscribeEventCalled = false; s.ClientUnsubscribedTopic += (_, e) => { @@ -122,12 +122,11 @@ namespace MQTTnet.Core.Tests }; await c1.UnsubscribeAsync("a"); - await Task.Delay(100); + await Task.Delay(500); Assert.IsTrue(unsubscribeEventCalled, "Unsubscribe event not called."); await c2.PublishAsync(message); - - await Task.Delay(500); + await Task.Delay(1000); Assert.AreEqual(1, receivedMessagesCount); } finally @@ -406,7 +405,7 @@ namespace MQTTnet.Core.Tests await c1.SubscribeAsync("A", MqttQualityOfServiceLevel.AtMostOnce); await c2.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("A").WithPayload(Encoding.UTF8.GetBytes("The body")).Build()); - await Task.Delay(500); + await Task.Delay(1000); } finally { From 9b29f8c23a5417650c7f5e03ee4fa22e97947e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Warin?= Date: Fri, 18 May 2018 17:10:28 +0200 Subject: [PATCH 056/138] Fix MqttProtocolVersion 3.1 connection --- .../MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 12330b5d..943d49e1 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -205,11 +205,7 @@ namespace MQTTnet.Serializer } else { - var buffer = new byte[6]; - Array.Copy(protocolName, buffer, 4); - protocolName = stream.ReadBytes(2); - Array.Copy(protocolName, 0, buffer, 4, 2); - + protocolName = protocolName.Concat(stream.ReadBytes(2)).ToArray(); if (protocolName.SequenceEqual(ProtocolVersionV310Name)) { protocolVersion = MqttProtocolVersion.V310; From f844a8be5ee1293a5e5d9ed133e3e55dfb72d2a1 Mon Sep 17 00:00:00 2001 From: JTrotta Date: Tue, 29 May 2018 11:32:16 +0200 Subject: [PATCH 057/138] ManagedMqttClietStorage now works --- .../MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs | 4 ++++ .../ManagedClient/ManagedMqttClientStorageManager.cs | 7 +++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs index f1ce45d9..9d0f72a3 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs @@ -66,6 +66,10 @@ namespace MQTTnet.ManagedClient { _storageManager = new ManagedMqttClientStorageManager(_options.Storage); await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false); + foreach (var loadedMessage in _storageManager.ApplicationMessages) + { + _messageQueue.Add(loadedMessage); + } } _connectionCancellationToken = new CancellationTokenSource(); diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs index 0e71776e..156fba0b 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs +++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs @@ -11,6 +11,8 @@ namespace MQTTnet.ManagedClient private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly IManagedMqttClientStorage _storage; + public List ApplicationMessages => _applicationMessages; + public ManagedMqttClientStorageManager(IManagedMqttClientStorage storage) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); @@ -19,10 +21,7 @@ namespace MQTTnet.ManagedClient public async Task LoadQueuedMessagesAsync() { var loadedMessages = await _storage.LoadQueuedMessagesAsync().ConfigureAwait(false); - foreach (var loadedMessage in loadedMessages) - { - _applicationMessages.Add(loadedMessage); - } + _applicationMessages.AddRange(loadedMessages); } public async Task AddAsync(MqttApplicationMessage applicationMessage) From 137d1d181bd45e8e676b78c3004b246e4a1e98f8 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 29 May 2018 21:21:30 +0200 Subject: [PATCH 058/138] Refactor ManagedMqttClientStorage --- .../ManagedClient/ManagedMqttClient.cs | 7 ++-- .../ManagedMqttClientStorageManager.cs | 36 ++++++++----------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs index 8b636945..7cad3560 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs @@ -68,10 +68,11 @@ namespace MQTTnet.ManagedClient if (_options.Storage != null) { _storageManager = new ManagedMqttClientStorageManager(_options.Storage); - await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false); - foreach (var loadedMessage in _storageManager.ApplicationMessages) + var messages = await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false); + + foreach (var message in messages) { - _messageQueue.Add(loadedMessage); + _messageQueue.Add(message); } } diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs index 156fba0b..49eaae34 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs +++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs @@ -2,65 +2,57 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using MQTTnet.Internal; namespace MQTTnet.ManagedClient { public class ManagedMqttClientStorageManager { - private readonly List _applicationMessages = new List(); - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); - private readonly IManagedMqttClientStorage _storage; + private readonly List _messages = new List(); + private readonly AsyncLock _messagesLock = new AsyncLock(); - public List ApplicationMessages => _applicationMessages; + private readonly IManagedMqttClientStorage _storage; public ManagedMqttClientStorageManager(IManagedMqttClientStorage storage) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); } - public async Task LoadQueuedMessagesAsync() + public async Task> LoadQueuedMessagesAsync() { var loadedMessages = await _storage.LoadQueuedMessagesAsync().ConfigureAwait(false); - _applicationMessages.AddRange(loadedMessages); + _messages.AddRange(loadedMessages); + + return _messages; } public async Task AddAsync(MqttApplicationMessage applicationMessage) { - await _semaphore.WaitAsync().ConfigureAwait(false); - try + using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { - _applicationMessages.Add(applicationMessage); + _messages.Add(applicationMessage); await SaveAsync().ConfigureAwait(false); } - finally - { - _semaphore.Release(); - } } public async Task RemoveAsync(MqttApplicationMessage applicationMessage) { - await _semaphore.WaitAsync().ConfigureAwait(false); - try + using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { - var index = _applicationMessages.IndexOf(applicationMessage); + var index = _messages.IndexOf(applicationMessage); if (index == -1) { return; } - _applicationMessages.RemoveAt(index); + _messages.RemoveAt(index); await SaveAsync().ConfigureAwait(false); } - finally - { - _semaphore.Release(); - } } private Task SaveAsync() { - return _storage.SaveQueuedMessagesAsync(_applicationMessages); + return _storage.SaveQueuedMessagesAsync(_messages); } } } From 06886fe6c2ba6aa7c3caf09f1ece531b4feba353 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 29 May 2018 21:21:41 +0200 Subject: [PATCH 059/138] Update UWP version for RPC extension --- .../MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj b/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj index 3f4128e0..fa915cf4 100644 --- a/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj +++ b/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj @@ -1,4 +1,4 @@ - + netstandard1.3;netstandard2.0;net452;net461;uap10.0 @@ -15,7 +15,7 @@ false UAP,Version=v10.0 UAP - 10.0.16299.0 + 10.0.17134.0 10.0.10240.0 .NETCore v5.0 From c1297eadd4ae7e182371c869ff1f0b6ecc4625e0 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 29 May 2018 21:22:46 +0200 Subject: [PATCH 060/138] Update docs --- Build/MQTTnet.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 8f496adb..cf7e64ca 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -13,6 +13,7 @@ * [Core] Performance optimizations. * [Core] Due to performance reasons the timestamp of log messages is now in UTC format. * [Client] Received messages are now processed in the worker thread by default. Added a new setting for switching back to dedicated threads. +* [ManagedClient] Fixed a loading issue of stored application messages (thanks to @JTrotta). * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot). * [Server] The takeover of an existing client sessions is now treated as a _clean_ disconnect of the previous client. * [Server] The pending messages queue per client is now limited to 250 messages. Overflow strategy and count can be changed via options (thanks to @VladimirAkopyan) From b5a43c416acda38d4f53a11e72347fbf35c6c99e Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 29 May 2018 21:39:40 +0200 Subject: [PATCH 061/138] Add validation of packet control type. --- .../Serializer/MqttPacketReader.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs index 41239246..57755df9 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs @@ -12,7 +12,7 @@ namespace MQTTnet.Serializer { public static class MqttPacketReader { - public static async Task ReadHeaderAsync(IMqttChannel stream, CancellationToken cancellationToken) + public static async Task ReadHeaderAsync(IMqttChannel channel, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -23,20 +23,26 @@ namespace MQTTnet.Serializer // some large delay and thus the thread should be put back to the pool (await). So ReadByte() // is not an option here. var buffer = new byte[1]; - var readCount = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + var readCount = await channel.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); if (readCount <= 0) { return null; } var fixedHeader = buffer[0]; - var controlPacketType = (MqttControlPacketType)(fixedHeader >> 4); - var bodyLength = await ReadBodyLengthAsync(stream, cancellationToken).ConfigureAwait(false); + var controlPacketType = fixedHeader >> 4; + + if (controlPacketType < 1 || controlPacketType > 14) + { + throw new MqttProtocolViolationException($"The packet type is invalid ({controlPacketType})."); + } + + var bodyLength = await ReadBodyLengthAsync(channel, cancellationToken).ConfigureAwait(false); return new MqttPacketHeader { FixedHeader = fixedHeader, - ControlPacketType = controlPacketType, + ControlPacketType = (MqttControlPacketType)controlPacketType, BodyLength = bodyLength }; } From 4838da48ef1731ab02825a21cd0df57a42b1a9a3 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 29 May 2018 21:45:25 +0200 Subject: [PATCH 062/138] Replace a manual lock with the lock class. --- .../ManagedClient/ManagedMqttClient.cs | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs index 7cad3560..804b1861 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using MQTTnet.Client; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; +using MQTTnet.Internal; using MQTTnet.Protocol; namespace MQTTnet.ManagedClient @@ -15,7 +16,7 @@ namespace MQTTnet.ManagedClient { private readonly BlockingCollection _messageQueue = new BlockingCollection(); private readonly Dictionary _subscriptions = new Dictionary(); - private readonly SemaphoreSlim _subscriptionsSemaphore = new SemaphoreSlim(1, 1); + private readonly AsyncLock _subscriptionsLock = new AsyncLock(); private readonly List _unsubscriptions = new List(); private readonly IMqttClient _mqttClient; @@ -117,8 +118,7 @@ namespace MQTTnet.ManagedClient { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false); - try + using (await _subscriptionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { foreach (var topicFilter in topicFilters) { @@ -126,16 +126,11 @@ namespace MQTTnet.ManagedClient _subscriptionsNotPushed = true; } } - finally - { - _subscriptionsSemaphore.Release(); - } } public async Task UnsubscribeAsync(IEnumerable topics) { - await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false); - try + using (await _subscriptionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { foreach (var topic in topics) { @@ -146,16 +141,12 @@ namespace MQTTnet.ManagedClient } } } - finally - { - _subscriptionsSemaphore.Release(); - } } public void Dispose() { _messageQueue?.Dispose(); - _subscriptionsSemaphore?.Dispose(); + _subscriptionsLock?.Dispose(); _connectionCancellationToken?.Dispose(); _publishingCancellationToken?.Dispose(); } @@ -294,8 +285,7 @@ namespace MQTTnet.ManagedClient List subscriptions; List unsubscriptions; - await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false); - try + using (await _subscriptionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList(); @@ -304,10 +294,6 @@ namespace MQTTnet.ManagedClient _subscriptionsNotPushed = false; } - finally - { - _subscriptionsSemaphore.Release(); - } if (!subscriptions.Any() && !unsubscriptions.Any()) { From 62e9a333c78e20618ef9fa323bb29aeb6e186630 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 29 May 2018 22:21:16 +0200 Subject: [PATCH 063/138] Add several packet validations. --- .../Adapter/MqttChannelAdapter.cs | 2 +- .../Adapter/ReceivedMqttPacket.cs | 2 +- .../Serializer/MqttPacketSerializer.cs | 194 ++++++++++-------- 3 files changed, 115 insertions(+), 83 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index ee2f586c..f6abb8d6 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -137,7 +137,7 @@ namespace MQTTnet.Adapter if (header.BodyLength == 0) { - return new ReceivedMqttPacket(header, new MemoryStream(new byte[0], false)); + return new ReceivedMqttPacket(header, null); } var body = new MemoryStream(header.BodyLength); diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs b/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs index a44fb54e..24651fea 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs @@ -9,7 +9,7 @@ namespace MQTTnet.Adapter public ReceivedMqttPacket(MqttPacketHeader header, MemoryStream body) { Header = header ?? throw new ArgumentNullException(nameof(header)); - Body = body ?? throw new ArgumentNullException(nameof(body)); + Body = body; } public MqttPacketHeader Header { get; } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 943d49e1..75da85d7 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -4,15 +4,11 @@ using MQTTnet.Protocol; using System; using System.IO; using System.Linq; -using System.Text; namespace MQTTnet.Serializer { public sealed class MqttPacketSerializer : IMqttPacketSerializer { - private static byte[] ProtocolVersionV311Name { get; } = Encoding.UTF8.GetBytes("MQTT"); - private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIsdp"); - public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; public ArraySegment Serialize(MqttBasePacket packet) @@ -67,116 +63,132 @@ namespace MQTTnet.Serializer } } - public MqttBasePacket Deserialize(MqttPacketHeader header, Stream stream) + public MqttBasePacket Deserialize(MqttPacketHeader header, Stream body) { if (header == null) throw new ArgumentNullException(nameof(header)); - if (stream == null) throw new ArgumentNullException(nameof(stream)); + if (body == null) throw new ArgumentNullException(nameof(body)); switch (header.ControlPacketType) { - case MqttControlPacketType.Connect: return DeserializeConnect(stream); - case MqttControlPacketType.ConnAck: return DeserializeConnAck(stream); + case MqttControlPacketType.Connect: return DeserializeConnect(body); + case MqttControlPacketType.ConnAck: return DeserializeConnAck(body); case MqttControlPacketType.Disconnect: return new MqttDisconnectPacket(); - case MqttControlPacketType.Publish: return DeserializePublish(stream, header); - case MqttControlPacketType.PubAck: return DeserializePubAck(stream); - case MqttControlPacketType.PubRec: return DeserializePubRec(stream); - case MqttControlPacketType.PubRel: return DeserializePubRel(stream); - case MqttControlPacketType.PubComp: return DeserializePubComp(stream); + case MqttControlPacketType.Publish: return DeserializePublish(header, body); + case MqttControlPacketType.PubAck: return DeserializePubAck(body); + case MqttControlPacketType.PubRec: return DeserializePubRec(body); + case MqttControlPacketType.PubRel: return DeserializePubRel(body); + case MqttControlPacketType.PubComp: return DeserializePubComp(body); case MqttControlPacketType.PingReq: return new MqttPingReqPacket(); case MqttControlPacketType.PingResp: return new MqttPingRespPacket(); - case MqttControlPacketType.Subscribe: return DeserializeSubscribe(stream, header); - case MqttControlPacketType.SubAck: return DeserializeSubAck(stream, header); - case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(stream, header); - case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(stream); + case MqttControlPacketType.Subscribe: return DeserializeSubscribe(header, body); + case MqttControlPacketType.SubAck: return DeserializeSubAck(header, body); + case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(header, body); + case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(body); default: throw new MqttProtocolViolationException($"Packet type ({(int)header.ControlPacketType}) not supported."); } } - private static MqttBasePacket DeserializeUnsubAck(Stream stream) + private static MqttBasePacket DeserializeUnsubAck(Stream body) { + ThrowIfBodyIsEmpty(body); + return new MqttUnsubAckPacket { - PacketIdentifier = stream.ReadUInt16() + PacketIdentifier = body.ReadUInt16() }; } - private static MqttBasePacket DeserializePubComp(Stream stream) + private static MqttBasePacket DeserializePubComp(Stream body) { + ThrowIfBodyIsEmpty(body); + return new MqttPubCompPacket { - PacketIdentifier = stream.ReadUInt16() + PacketIdentifier = body.ReadUInt16() }; } - private static MqttBasePacket DeserializePubRel(Stream stream) + private static MqttBasePacket DeserializePubRel(Stream body) { + ThrowIfBodyIsEmpty(body); + return new MqttPubRelPacket { - PacketIdentifier = stream.ReadUInt16() + PacketIdentifier = body.ReadUInt16() }; } - private static MqttBasePacket DeserializePubRec(Stream stream) + private static MqttBasePacket DeserializePubRec(Stream body) { + ThrowIfBodyIsEmpty(body); + return new MqttPubRecPacket { - PacketIdentifier = stream.ReadUInt16() + PacketIdentifier = body.ReadUInt16() }; } - private static MqttBasePacket DeserializePubAck(Stream stream) + private static MqttBasePacket DeserializePubAck(Stream body) { + ThrowIfBodyIsEmpty(body); + return new MqttPubAckPacket { - PacketIdentifier = stream.ReadUInt16() + PacketIdentifier = body.ReadUInt16() }; } - private static MqttBasePacket DeserializeUnsubscribe(Stream stream, MqttPacketHeader header) + private static MqttBasePacket DeserializeUnsubscribe(MqttPacketHeader header, Stream body) { + ThrowIfBodyIsEmpty(body); + var packet = new MqttUnsubscribePacket { - PacketIdentifier = stream.ReadUInt16(), + PacketIdentifier = body.ReadUInt16(), }; - while (stream.Position != header.BodyLength) + while (body.Position != header.BodyLength) { - packet.TopicFilters.Add(stream.ReadStringWithLengthPrefix()); + packet.TopicFilters.Add(body.ReadStringWithLengthPrefix()); } return packet; } - private static MqttBasePacket DeserializeSubscribe(Stream stream, MqttPacketHeader header) + private static MqttBasePacket DeserializeSubscribe(MqttPacketHeader header, Stream body) { + ThrowIfBodyIsEmpty(body); + var packet = new MqttSubscribePacket { - PacketIdentifier = stream.ReadUInt16() + PacketIdentifier = body.ReadUInt16() }; - while (stream.Position != header.BodyLength) + while (body.Position != header.BodyLength) { packet.TopicFilters.Add(new TopicFilter( - stream.ReadStringWithLengthPrefix(), - (MqttQualityOfServiceLevel)stream.ReadByte())); + body.ReadStringWithLengthPrefix(), + (MqttQualityOfServiceLevel)body.ReadByte())); } return packet; } - private static MqttBasePacket DeserializePublish(Stream stream, MqttPacketHeader mqttPacketHeader) + private static MqttBasePacket DeserializePublish(MqttPacketHeader header, Stream body) { - var fixedHeader = new ByteReader(mqttPacketHeader.FixedHeader); + ThrowIfBodyIsEmpty(body); + + var fixedHeader = new ByteReader(header.FixedHeader); var retain = fixedHeader.Read(); var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2); var dup = fixedHeader.Read(); - var topic = stream.ReadStringWithLengthPrefix(); + var topic = body.ReadStringWithLengthPrefix(); ushort? packetIdentifier = null; if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { - packetIdentifier = stream.ReadUInt16(); + packetIdentifier = body.ReadUInt16(); } var packet = new MqttPublishPacket @@ -184,7 +196,7 @@ namespace MQTTnet.Serializer PacketIdentifier = packetIdentifier, Retain = retain, Topic = topic, - Payload = stream.ReadRemainingData(mqttPacketHeader), + Payload = body.ReadRemainingData(header), QualityOfServiceLevel = qualityOfServiceLevel, Dup = dup }; @@ -192,57 +204,65 @@ namespace MQTTnet.Serializer return packet; } - private static MqttBasePacket DeserializeConnect(Stream stream) + private static MqttBasePacket DeserializeConnect(Stream body) { - stream.ReadBytes(2); // Skip 2 bytes for header and remaining length. + ThrowIfBodyIsEmpty(body); + + var protocolName = body.ReadStringWithLengthPrefix(); MqttProtocolVersion protocolVersion; - var protocolName = stream.ReadBytes(4); - - if (protocolName.SequenceEqual(ProtocolVersionV311Name)) + if (protocolName == "MQTT") { + var protocolLevel = body.ReadByte(); + if (protocolLevel != 4) + { + throw new MqttProtocolViolationException($"Protocol level ({protocolLevel}) not supported for MQTT 3.1.1."); + } + protocolVersion = MqttProtocolVersion.V311; } + else if (protocolName == "MQIsdp") + { + var protocolLevel = body.ReadByte(); + if (protocolLevel != 3) + { + throw new MqttProtocolViolationException($"Protocol level ({protocolLevel}) not supported for MQTT 3.1."); + } + + protocolVersion = MqttProtocolVersion.V310; + } else { - protocolName = protocolName.Concat(stream.ReadBytes(2)).ToArray(); - if (protocolName.SequenceEqual(ProtocolVersionV310Name)) - { - protocolVersion = MqttProtocolVersion.V310; - } - else - { - throw new MqttProtocolViolationException("Protocol name is not supported."); - } + throw new MqttProtocolViolationException($"Protocol name ({protocolName}) is not supported."); } - stream.ReadByte(); // Skip protocol level - var connectFlags = stream.ReadByte(); - - var connectFlagsReader = new ByteReader(connectFlags); - connectFlagsReader.Read(); // Reserved. + var connectFlags = new ByteReader(body.ReadByte()); + if (connectFlags.Read()) + { + throw new MqttProtocolViolationException("The first bit of the Connect Flags must be set to 0."); + } var packet = new MqttConnectPacket { ProtocolVersion = protocolVersion, - CleanSession = connectFlagsReader.Read() + CleanSession = connectFlags.Read() }; - var willFlag = connectFlagsReader.Read(); - var willQoS = connectFlagsReader.Read(2); - var willRetain = connectFlagsReader.Read(); - var passwordFlag = connectFlagsReader.Read(); - var usernameFlag = connectFlagsReader.Read(); + var willFlag = connectFlags.Read(); + var willQoS = connectFlags.Read(2); + var willRetain = connectFlags.Read(); + var passwordFlag = connectFlags.Read(); + var usernameFlag = connectFlags.Read(); - packet.KeepAlivePeriod = stream.ReadUInt16(); - packet.ClientId = stream.ReadStringWithLengthPrefix(); + packet.KeepAlivePeriod = body.ReadUInt16(); + packet.ClientId = body.ReadStringWithLengthPrefix(); if (willFlag) { packet.WillMessage = new MqttApplicationMessage { - Topic = stream.ReadStringWithLengthPrefix(), - Payload = stream.ReadWithLengthPrefix(), + Topic = body.ReadStringWithLengthPrefix(), + Payload = body.ReadWithLengthPrefix(), QualityOfServiceLevel = (MqttQualityOfServiceLevel)willQoS, Retain = willRetain }; @@ -250,45 +270,49 @@ namespace MQTTnet.Serializer if (usernameFlag) { - packet.Username = stream.ReadStringWithLengthPrefix(); + packet.Username = body.ReadStringWithLengthPrefix(); } if (passwordFlag) { - packet.Password = stream.ReadStringWithLengthPrefix(); + packet.Password = body.ReadStringWithLengthPrefix(); } ValidateConnectPacket(packet); return packet; } - private static MqttBasePacket DeserializeSubAck(Stream stream, MqttPacketHeader header) + private static MqttBasePacket DeserializeSubAck(MqttPacketHeader header, Stream body) { + ThrowIfBodyIsEmpty(body); + var packet = new MqttSubAckPacket { - PacketIdentifier = stream.ReadUInt16() + PacketIdentifier = body.ReadUInt16() }; - while (stream.Position != header.BodyLength) + while (body.Position != header.BodyLength) { - packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)stream.ReadByte()); + packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)body.ReadByte()); } return packet; } - private MqttBasePacket DeserializeConnAck(Stream stream) + private MqttBasePacket DeserializeConnAck(Stream body) { + ThrowIfBodyIsEmpty(body); + var packet = new MqttConnAckPacket(); - var firstByteReader = new ByteReader(stream.ReadByte()); + var firstByteReader = new ByteReader(body.ReadByte()); if (ProtocolVersion == MqttProtocolVersion.V311) { packet.IsSessionPresent = firstByteReader.Read(); } - packet.ConnectReturnCode = (MqttConnectReturnCode)stream.ReadByte(); + packet.ConnectReturnCode = (MqttConnectReturnCode)body.ReadByte(); return packet; } @@ -320,12 +344,12 @@ namespace MQTTnet.Serializer // Write variable header if (ProtocolVersion == MqttProtocolVersion.V311) { - stream.WriteWithLengthPrefix(ProtocolVersionV311Name); + stream.WriteWithLengthPrefix("MQTT"); stream.WriteByte(0x04); // 3.1.2.2 Protocol Level 4 } else { - stream.WriteWithLengthPrefix(ProtocolVersionV310Name); + stream.WriteWithLengthPrefix("MQIsdp"); stream.WriteByte(0x03); // Protocol Level 3 } @@ -571,5 +595,13 @@ namespace MQTTnet.Serializer { return MqttPacketWriter.BuildFixedHeader(type); } + + private static void ThrowIfBodyIsEmpty(Stream body) + { + if (body == null || body.Length == 0) + { + throw new MqttProtocolViolationException("Data from the body is required but not present."); + } + } } } From 7e0017d14a7bf1b1d636980e0d444716b2e8993c Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 29 May 2018 22:29:28 +0200 Subject: [PATCH 064/138] Update docs. --- Build/MQTTnet.nuspec | 1 + .../MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index cf7e64ca..033b3ab6 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -12,6 +12,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). * [Core] Performance optimizations. * [Core] Due to performance reasons the timestamp of log messages is now in UTC format. +* [Core] Added several packet validations. * [Client] Received messages are now processed in the worker thread by default. Added a new setting for switching back to dedicated threads. * [ManagedClient] Fixed a loading issue of stored application messages (thanks to @JTrotta). * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot). diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 75da85d7..cfe80108 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -345,12 +345,12 @@ namespace MQTTnet.Serializer if (ProtocolVersion == MqttProtocolVersion.V311) { stream.WriteWithLengthPrefix("MQTT"); - stream.WriteByte(0x04); // 3.1.2.2 Protocol Level 4 + stream.WriteByte(4); // 3.1.2.2 Protocol Level 4 } else { stream.WriteWithLengthPrefix("MQIsdp"); - stream.WriteByte(0x03); // Protocol Level 3 + stream.WriteByte(3); // Protocol Level 3 } var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags @@ -410,6 +410,7 @@ namespace MQTTnet.Serializer { var connectAcknowledgeFlags = new ByteWriter(); connectAcknowledgeFlags.Write(packet.IsSessionPresent); + stream.Write(connectAcknowledgeFlags); } else From a10183628bf4abc80f0699695c58eaf81e48dace Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 29 May 2018 23:13:53 +0200 Subject: [PATCH 065/138] Suspend keep alive checking while a packet is being received. --- Build/MQTTnet.nuspec | 1 + .../Adapter/IMqttChannelAdapter.cs | 6 +- .../Adapter/MqttChannelAdapter.cs | 66 +++++++++++-------- .../Adapter/ReceivedMqttPacket.cs | 7 +- .../Packets/MqttPacketHeader.cs | 13 ---- .../Serializer/IMqttPacketSerializer.cs | 4 +- .../Serializer/MqttPacketReader.cs | 29 ++------ .../Serializer/MqttPacketSerializer.cs | 58 +++++++++------- .../Server/MqttClientKeepAliveMonitor.cs | 36 +++++----- .../Server/MqttClientSession.cs | 18 +++++ .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 10 ++- .../MqttPacketReaderTests.cs | 4 +- .../MqttPacketSerializerTests.cs | 9 ++- .../TestMqttCommunicationAdapter.cs | 3 + 14 files changed, 146 insertions(+), 118 deletions(-) delete mode 100644 Frameworks/MQTTnet.NetStandard/Packets/MqttPacketHeader.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 033b3ab6..88dc7027 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -18,6 +18,7 @@ * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot). * [Server] The takeover of an existing client sessions is now treated as a _clean_ disconnect of the previous client. * [Server] The pending messages queue per client is now limited to 250 messages. Overflow strategy and count can be changed via options (thanks to @VladimirAkopyan) +* [Server] Keep alive checking is now suspended while large packages are being received (and thus the client is connected). Keep alive checking continues after a large packet is received completely. Copyright Christian Kratky 2016-2018 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 diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs index a08d9f67..3a99add2 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs @@ -12,7 +12,11 @@ namespace MQTTnet.Adapter string Endpoint { get; } IMqttPacketSerializer PacketSerializer { get; } - + + event EventHandler ReadingPacketStarted; + + event EventHandler ReadingPacketCompleted; + Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken); Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken); diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index f6abb8d6..157e7653 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -37,6 +37,9 @@ namespace MQTTnet.Adapter public IMqttPacketSerializer PacketSerializer { get; } + public event EventHandler ReadingPacketStarted; + public event EventHandler ReadingPacketCompleted; + public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken) { ThrowIfDisposed(); @@ -110,7 +113,7 @@ namespace MQTTnet.Adapter throw new TaskCanceledException(); } - packet = PacketSerializer.Deserialize(receivedMqttPacket.Header, receivedMqttPacket.Body); + packet = PacketSerializer.Deserialize(receivedMqttPacket); if (packet == null) { throw new MqttProtocolViolationException("Received malformed packet."); @@ -127,46 +130,55 @@ namespace MQTTnet.Adapter return packet; } - private static async Task ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken) + private async Task ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken) { - var header = await MqttPacketReader.ReadHeaderAsync(channel, cancellationToken).ConfigureAwait(false); - if (header == null) + var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, cancellationToken).ConfigureAwait(false); + if (!fixedHeader.HasValue) { return null; } - - if (header.BodyLength == 0) - { - return new ReceivedMqttPacket(header, null); - } - var body = new MemoryStream(header.BodyLength); - - var buffer = new byte[Math.Min(ReadBufferSize, header.BodyLength)]; - while (body.Length < header.BodyLength) + ReadingPacketStarted?.Invoke(this, EventArgs.Empty); + try { - var bytesLeft = header.BodyLength - (int)body.Length; - if (bytesLeft > buffer.Length) + var bodyLength = await MqttPacketReader.ReadBodyLengthAsync(channel, cancellationToken).ConfigureAwait(false); + if (bodyLength == 0) { - bytesLeft = buffer.Length; + return new ReceivedMqttPacket(fixedHeader.Value, null); } - var readBytesCount = await channel.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false); + var body = new MemoryStream(bodyLength); - // Check if the client closed the connection before sending the full body. - if (readBytesCount == 0) + var buffer = new byte[Math.Min(ReadBufferSize, bodyLength)]; + while (body.Length < bodyLength) { - throw new MqttCommunicationException("Connection closed while reading remaining packet body."); + var bytesLeft = bodyLength - (int)body.Length; + if (bytesLeft > buffer.Length) + { + bytesLeft = buffer.Length; + } + + var readBytesCount = await channel.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false); + + // Check if the client closed the connection before sending the full body. + if (readBytesCount <= 0) + { + throw new MqttCommunicationException("Connection closed while reading remaining packet body."); + } + + // Here is no need to await because internally only an array is used and no real I/O operation is made. + // Using async here will only generate overhead. + body.Write(buffer, 0, readBytesCount); } - // Here is no need to await because internally only an array is used and no real I/O operation is made. - // Using async here will only generate overhead. - body.Write(buffer, 0, readBytesCount); + body.Seek(0L, SeekOrigin.Begin); + + return new ReceivedMqttPacket(fixedHeader.Value, body); + } + finally + { + ReadingPacketCompleted?.Invoke(this, EventArgs.Empty); } - - body.Seek(0L, SeekOrigin.Begin); - - return new ReceivedMqttPacket(header, body); } private static async Task ExecuteAndWrapExceptionAsync(Func action) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs b/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs index 24651fea..c6768f99 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs @@ -1,18 +1,17 @@ using System; using System.IO; -using MQTTnet.Packets; namespace MQTTnet.Adapter { public sealed class ReceivedMqttPacket : IDisposable { - public ReceivedMqttPacket(MqttPacketHeader header, MemoryStream body) + public ReceivedMqttPacket(byte fixedHeader, MemoryStream body) { - Header = header ?? throw new ArgumentNullException(nameof(header)); + FixedHeader = fixedHeader; Body = body; } - public MqttPacketHeader Header { get; } + public byte FixedHeader { get; } public MemoryStream Body { get; } diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPacketHeader.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPacketHeader.cs deleted file mode 100644 index 01c26ecd..00000000 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttPacketHeader.cs +++ /dev/null @@ -1,13 +0,0 @@ -using MQTTnet.Protocol; - -namespace MQTTnet.Packets -{ - public class MqttPacketHeader - { - public MqttControlPacketType ControlPacketType { get; set; } - - public byte FixedHeader { get; set; } - - public int BodyLength { get; set; } - } -} diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs index 36883303..a81071bd 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs @@ -1,5 +1,5 @@ using System; -using System.IO; +using MQTTnet.Adapter; using MQTTnet.Packets; namespace MQTTnet.Serializer @@ -10,6 +10,6 @@ namespace MQTTnet.Serializer ArraySegment Serialize(MqttBasePacket mqttPacket); - MqttBasePacket Deserialize(MqttPacketHeader header, Stream body); + MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket); } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs index 57755df9..432dc90e 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs @@ -5,14 +5,12 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Channel; using MQTTnet.Exceptions; -using MQTTnet.Packets; -using MQTTnet.Protocol; namespace MQTTnet.Serializer { public static class MqttPacketReader { - public static async Task ReadHeaderAsync(IMqttChannel channel, CancellationToken cancellationToken) + public static async Task ReadFixedHeaderAsync(IMqttChannel channel, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -29,22 +27,7 @@ namespace MQTTnet.Serializer return null; } - var fixedHeader = buffer[0]; - var controlPacketType = fixedHeader >> 4; - - if (controlPacketType < 1 || controlPacketType > 14) - { - throw new MqttProtocolViolationException($"The packet type is invalid ({controlPacketType})."); - } - - var bodyLength = await ReadBodyLengthAsync(channel, cancellationToken).ConfigureAwait(false); - - return new MqttPacketHeader - { - FixedHeader = fixedHeader, - ControlPacketType = (MqttControlPacketType)controlPacketType, - BodyLength = bodyLength - }; + return buffer[0]; } public static ushort ReadUInt16(this Stream stream) @@ -80,9 +63,9 @@ namespace MQTTnet.Serializer return stream.ReadBytes(length); } - public static byte[] ReadRemainingData(this Stream stream, MqttPacketHeader header) + public static byte[] ReadRemainingData(this Stream stream) { - return stream.ReadBytes(header.BodyLength - (int)stream.Position); + return stream.ReadBytes((int)(stream.Length - stream.Position)); } public static byte[] ReadBytes(this Stream stream, int count) @@ -92,7 +75,7 @@ namespace MQTTnet.Serializer return buffer; } - private static async Task ReadBodyLengthAsync(IMqttChannel stream, CancellationToken cancellationToken) + public static async Task ReadBodyLengthAsync(IMqttChannel channel, 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 = 1; @@ -107,7 +90,7 @@ namespace MQTTnet.Serializer throw new TaskCanceledException(); } - var readCount = await stream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); + var readCount = await channel.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); if (readCount <= 0) { throw new MqttCommunicationException("Connection closed while reading remaining length data."); diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index cfe80108..369bd16a 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -4,6 +4,7 @@ using MQTTnet.Protocol; using System; using System.IO; using System.Linq; +using MQTTnet.Adapter; namespace MQTTnet.Serializer { @@ -63,28 +64,34 @@ namespace MQTTnet.Serializer } } - public MqttBasePacket Deserialize(MqttPacketHeader header, Stream body) + public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) { - if (header == null) throw new ArgumentNullException(nameof(header)); - if (body == null) throw new ArgumentNullException(nameof(body)); + if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket)); - switch (header.ControlPacketType) + var controlPacketType = receivedMqttPacket.FixedHeader >> 4; + if (controlPacketType < 1 || controlPacketType > 14) { - case MqttControlPacketType.Connect: return DeserializeConnect(body); - case MqttControlPacketType.ConnAck: return DeserializeConnAck(body); + throw new MqttProtocolViolationException($"The packet type is invalid ({controlPacketType})."); + } + + switch ((MqttControlPacketType)controlPacketType) + { + case MqttControlPacketType.Connect: return DeserializeConnect(receivedMqttPacket.Body); + case MqttControlPacketType.ConnAck: return DeserializeConnAck(receivedMqttPacket.Body); case MqttControlPacketType.Disconnect: return new MqttDisconnectPacket(); - case MqttControlPacketType.Publish: return DeserializePublish(header, body); - case MqttControlPacketType.PubAck: return DeserializePubAck(body); - case MqttControlPacketType.PubRec: return DeserializePubRec(body); - case MqttControlPacketType.PubRel: return DeserializePubRel(body); - case MqttControlPacketType.PubComp: return DeserializePubComp(body); + case MqttControlPacketType.Publish: return DeserializePublish(receivedMqttPacket); + case MqttControlPacketType.PubAck: return DeserializePubAck(receivedMqttPacket.Body); + case MqttControlPacketType.PubRec: return DeserializePubRec(receivedMqttPacket.Body); + case MqttControlPacketType.PubRel: return DeserializePubRel(receivedMqttPacket.Body); + case MqttControlPacketType.PubComp: return DeserializePubComp(receivedMqttPacket.Body); case MqttControlPacketType.PingReq: return new MqttPingReqPacket(); case MqttControlPacketType.PingResp: return new MqttPingRespPacket(); - case MqttControlPacketType.Subscribe: return DeserializeSubscribe(header, body); - case MqttControlPacketType.SubAck: return DeserializeSubAck(header, body); - case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(header, body); - case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(body); - default: throw new MqttProtocolViolationException($"Packet type ({(int)header.ControlPacketType}) not supported."); + case MqttControlPacketType.Subscribe: return DeserializeSubscribe(receivedMqttPacket.Body); + case MqttControlPacketType.SubAck: return DeserializeSubAck(receivedMqttPacket.Body); + case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(receivedMqttPacket.Body); + case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(receivedMqttPacket.Body); + + default: throw new MqttProtocolViolationException($"Packet type ({controlPacketType}) not supported."); } } @@ -138,7 +145,7 @@ namespace MQTTnet.Serializer }; } - private static MqttBasePacket DeserializeUnsubscribe(MqttPacketHeader header, Stream body) + private static MqttBasePacket DeserializeUnsubscribe(Stream body) { ThrowIfBodyIsEmpty(body); @@ -147,7 +154,7 @@ namespace MQTTnet.Serializer PacketIdentifier = body.ReadUInt16(), }; - while (body.Position != header.BodyLength) + while (body.Position != body.Length) { packet.TopicFilters.Add(body.ReadStringWithLengthPrefix()); } @@ -155,7 +162,7 @@ namespace MQTTnet.Serializer return packet; } - private static MqttBasePacket DeserializeSubscribe(MqttPacketHeader header, Stream body) + private static MqttBasePacket DeserializeSubscribe(Stream body) { ThrowIfBodyIsEmpty(body); @@ -164,7 +171,7 @@ namespace MQTTnet.Serializer PacketIdentifier = body.ReadUInt16() }; - while (body.Position != header.BodyLength) + while (body.Position != body.Length) { packet.TopicFilters.Add(new TopicFilter( body.ReadStringWithLengthPrefix(), @@ -174,11 +181,12 @@ namespace MQTTnet.Serializer return packet; } - private static MqttBasePacket DeserializePublish(MqttPacketHeader header, Stream body) + private static MqttBasePacket DeserializePublish(ReceivedMqttPacket receivedMqttPacket) { + var body = receivedMqttPacket.Body; ThrowIfBodyIsEmpty(body); - var fixedHeader = new ByteReader(header.FixedHeader); + var fixedHeader = new ByteReader(receivedMqttPacket.FixedHeader); var retain = fixedHeader.Read(); var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2); var dup = fixedHeader.Read(); @@ -196,7 +204,7 @@ namespace MQTTnet.Serializer PacketIdentifier = packetIdentifier, Retain = retain, Topic = topic, - Payload = body.ReadRemainingData(header), + Payload = body.ReadRemainingData(), QualityOfServiceLevel = qualityOfServiceLevel, Dup = dup }; @@ -282,7 +290,7 @@ namespace MQTTnet.Serializer return packet; } - private static MqttBasePacket DeserializeSubAck(MqttPacketHeader header, Stream body) + private static MqttBasePacket DeserializeSubAck(Stream body) { ThrowIfBodyIsEmpty(body); @@ -291,7 +299,7 @@ namespace MQTTnet.Serializer PacketIdentifier = body.ReadUInt16() }; - while (body.Position != header.BodyLength) + while (body.Position != body.Length) { packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)body.ReadByte()); } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs index d73b183c..82bcbdf6 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs @@ -16,6 +16,7 @@ namespace MQTTnet.Server private readonly string _clientId; private readonly Action _callback; + private bool _isPaused; private Task _workerTask; public MqttClientKeepAliveMonitor(string clientId, Action callback, IMqttNetChildLogger logger) @@ -41,10 +42,25 @@ namespace MQTTnet.Server _workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken), cancellationToken); } - ////public void WaitForCompletion() - ////{ - //// SpinWait.SpinUntil(() => _workerTask == null || _workerTask.IsCanceled || _workerTask.IsCompleted || _workerTask.IsFaulted); - ////} + public void Pause() + { + _isPaused = true; + } + + public void Resume() + { + _isPaused = false; + } + + public void PacketReceived(MqttBasePacket packet) + { + _lastPacketReceivedTracker.Restart(); + + if (!(packet is MqttPingReqPacket)) + { + _lastNonKeepAlivePacketReceivedTracker.Restart(); + } + } private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken) { @@ -56,7 +72,7 @@ namespace MQTTnet.Server while (!cancellationToken.IsCancellationRequested) { // Values described here: [MQTT-3.1.2-24]. - if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D) + if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D) { _logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientId); _callback(); @@ -79,15 +95,5 @@ namespace MQTTnet.Server _logger.Verbose("Client {0}: Stopped checking keep alive timeout.", _clientId); } } - - public void PacketReceived(MqttBasePacket packet) - { - _lastPacketReceivedTracker.Restart(); - - if (!(packet is MqttPingReqPacket)) - { - _lastNonKeepAlivePacketReceivedTracker.Restart(); - } - } } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs index c42a3007..6e537e9e 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs @@ -73,6 +73,8 @@ namespace MQTTnet.Server try { _adapter = adapter; + adapter.ReadingPacketStarted += OnAdapterReadingPacketStarted; + adapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted; _cancellationTokenSource = new CancellationTokenSource(); _wasCleanDisconnect = false; @@ -106,6 +108,12 @@ namespace MQTTnet.Server } finally { + if (_adapter != null) + { + _adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; + _adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; + } + _adapter = null; _cancellationTokenSource?.Dispose(); @@ -340,5 +348,15 @@ namespace MQTTnet.Server var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }; return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken); } + + private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) + { + _keepAliveMonitor?.Pause(); + } + + private void OnAdapterReadingPacketStarted(object sender, EventArgs e) + { + _keepAliveMonitor?.Resume(); + } } } diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index fdc03b68..1b2e2384 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -7,6 +7,7 @@ using BenchmarkDotNet.Attributes.Exporters; using System; using System.Threading; using System.IO; +using MQTTnet.Adapter; using MQTTnet.Core.Internal; namespace MQTTnet.Benchmarks @@ -48,11 +49,14 @@ namespace MQTTnet.Benchmarks { using (var headerStream = new MemoryStream(Join(_serializedPacket))) { - var header = MqttPacketReader.ReadHeaderAsync(new TestMqttChannel(headerStream), CancellationToken.None).GetAwaiter().GetResult(); + var channel = new TestMqttChannel(headerStream); - using (var bodyStream = new MemoryStream(Join(_serializedPacket), (int)headerStream.Position, header.BodyLength)) + var header = MqttPacketReader.ReadFixedHeaderAsync(new TestMqttChannel(headerStream), CancellationToken.None).GetAwaiter().GetResult(); + var bodyLength = MqttPacketReader.ReadBodyLengthAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); + + using (var bodyStream = new MemoryStream(Join(_serializedPacket), (int)headerStream.Position, bodyLength)) { - _serializer.Deserialize(header, bodyStream); + _serializer.Deserialize(new ReceivedMqttPacket((byte)header, bodyStream)); } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs index 72a675bf..076dc5f9 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs @@ -12,9 +12,9 @@ namespace MQTTnet.Core.Tests [TestMethod] public void MqttPacketReader_EmptyStream() { - var header = MqttPacketReader.ReadHeaderAsync(new TestMqttChannel(new MemoryStream()), CancellationToken.None).GetAwaiter().GetResult(); + var header = MqttPacketReader.ReadFixedHeaderAsync(new TestMqttChannel(new MemoryStream()), CancellationToken.None).GetAwaiter().GetResult(); - Assert.IsNull(header); + Assert.AreEqual(-1, header); } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index a45736d2..606ea099 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Text; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Adapter; using MQTTnet.Core.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -416,11 +417,13 @@ namespace MQTTnet.Core.Tests using (var headerStream = new MemoryStream(Join(buffer1))) { - var header = MqttPacketReader.ReadHeaderAsync(new TestMqttChannel(headerStream), CancellationToken.None).GetAwaiter().GetResult(); + var channel = new TestMqttChannel(headerStream); + var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); + var bodyLength = MqttPacketReader.ReadBodyLengthAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); - using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.BodyLength)) + using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, bodyLength)) { - var deserializedPacket = serializer.Deserialize(header, bodyStream); + var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket((byte)header, bodyStream)); var buffer2 = serializer.Serialize(deserializedPacket); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2))); diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index 3b2dd1fb..8143728f 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -19,6 +19,9 @@ namespace MQTTnet.Core.Tests public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer(); + public event EventHandler ReadingPacketStarted; + public event EventHandler ReadingPacketCompleted; + public void Dispose() { } From 9bea46adcbc4f2ba6556de2bf6dba50ea8b5a149 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 30 May 2018 20:56:20 +0200 Subject: [PATCH 066/138] Remove unneeded _sealed_ keywords. Update docs. --- Build/MQTTnet.nuspec | 4 ++++ .../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 2 +- .../Adapter/MqttChannelAdapter.cs | 2 +- .../Adapter/ReceivedMqttPacket.cs | 2 +- .../Diagnostics/MqttNetChildLogger.cs | 2 +- .../Diagnostics/MqttNetLogMessage.cs | 2 +- .../MqttNetLogMessagePublishedEventArgs.cs | 2 +- .../Diagnostics/MqttNetLogger.cs | 2 +- .../MqttCommunicationTimedOutException.cs | 10 +++++++--- .../MqttProtocolViolationException.cs | 2 +- .../Implementations/MqttTcpChannel.Uwp.cs | 2 +- .../Implementations/MqttTcpChannel.cs | 2 +- .../Implementations/MqttWebSocketChannel.cs | 2 +- .../MQTTnet.NetStandard/Internal/AsyncLock.cs | 4 ++-- .../MqttApplicationMessage.cs | 18 ++---------------- .../MqttApplicationMessageReceivedEventArgs.cs | 2 +- .../Packets/MqttConnAckPacket.cs | 2 +- .../Packets/MqttConnectPacket.cs | 2 +- .../Packets/MqttDisconnectPacket.cs | 2 +- .../Packets/MqttPingReqPacket.cs | 2 +- .../Packets/MqttPingRespPacket.cs | 2 +- .../Packets/MqttPubAckPacket.cs | 2 +- .../Packets/MqttPubCompPacket.cs | 2 +- .../Packets/MqttPubRecPacket.cs | 2 +- .../Packets/MqttPubRelPacket.cs | 2 +- .../Packets/MqttPublishPacket.cs | 2 +- .../Packets/MqttSubAckPacket.cs | 2 +- .../Packets/MqttSubscribePacket.cs | 2 +- .../Packets/MqttUnsubAckPacket.cs | 2 +- .../Packets/MqttUnsubscribe.cs | 2 +- .../Serializer/ByteReader.cs | 2 +- .../Serializer/ByteWriter.cs | 2 +- .../Serializer/MqttPacketSerializer.cs | 2 +- .../Server/GetOrCreateClientSessionResult.cs | 2 +- .../Server/MqttClientKeepAliveMonitor.cs | 2 +- .../Server/MqttClientPendingMessagesQueue.cs | 2 +- .../Server/MqttClientSession.cs | 2 +- .../Server/MqttClientSessionsManager.cs | 2 +- .../Server/MqttClientSubscriptionsManager.cs | 2 +- .../Server/MqttRetainedMessagesManager.cs | 2 +- .../Server/MqttServerDefaultEndpointOptions.cs | 2 +- .../Server/MqttServerTlsEndpointOptions.cs | 2 +- Frameworks/MQTTnet.NetStandard/TopicFilter.cs | 2 +- 43 files changed, 54 insertions(+), 60 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 88dc7027..7dc215f1 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -13,12 +13,16 @@ * [Core] Performance optimizations. * [Core] Due to performance reasons the timestamp of log messages is now in UTC format. * [Core] Added several packet validations. +* [Core] Log messages now contain the complete source path including parent components. +* [Core] The adapter now has an _Endpoint_ definition as string containing remote IP and port. * [Client] Received messages are now processed in the worker thread by default. Added a new setting for switching back to dedicated threads. * [ManagedClient] Fixed a loading issue of stored application messages (thanks to @JTrotta). * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot). * [Server] The takeover of an existing client sessions is now treated as a _clean_ disconnect of the previous client. * [Server] The pending messages queue per client is now limited to 250 messages. Overflow strategy and count can be changed via options (thanks to @VladimirAkopyan) * [Server] Keep alive checking is now suspended while large packages are being received (and thus the client is connected). Keep alive checking continues after a large packet is received completely. +* [Server] Rewritten the _ConnectedClients_ API and added new features for disconnecting and Endpoint information (IP etc.). +* [Server] Added settings for disabling persistent sessions and defining a max pending messages queue size per session. Copyright Christian Kratky 2016-2018 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 diff --git a/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index e8242791..717f74ea 100644 --- a/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -9,7 +9,7 @@ using MQTTnet.Protocol; namespace MQTTnet.Extensions.Rpc { - public sealed class MqttRpcClient : IDisposable + public class MqttRpcClient : IDisposable { private readonly ConcurrentDictionary> _waitingCalls = new ConcurrentDictionary>(); private readonly IMqttClient _mqttClient; diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 157e7653..7b8f08a9 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -13,7 +13,7 @@ using MQTTnet.Serializer; namespace MQTTnet.Adapter { - public sealed class MqttChannelAdapter : IMqttChannelAdapter + public class MqttChannelAdapter : IMqttChannelAdapter { private const uint ErrorOperationAborted = 0x800703E3; private const int ReadBufferSize = 4096; // TODO: Move buffer size to config diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs b/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs index c6768f99..1f1c59a9 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs @@ -3,7 +3,7 @@ using System.IO; namespace MQTTnet.Adapter { - public sealed class ReceivedMqttPacket : IDisposable + public class ReceivedMqttPacket : IDisposable { public ReceivedMqttPacket(byte fixedHeader, MemoryStream body) { diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetChildLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetChildLogger.cs index 50557319..1ae5a9c3 100644 --- a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetChildLogger.cs +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetChildLogger.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Diagnostics { - public sealed class MqttNetChildLogger : IMqttNetChildLogger + public class MqttNetChildLogger : IMqttNetChildLogger { private readonly IMqttNetLogger _logger; private readonly string _source; diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessage.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessage.cs index 6035a89c..107d8f34 100644 --- a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessage.cs +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessage.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Diagnostics { - public sealed class MqttNetLogMessage + public class MqttNetLogMessage { public MqttNetLogMessage(string logId, DateTime timestamp, int threadId, string source, MqttNetLogLevel level, string message, Exception exception) { diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs index 2407752b..8a0c0fcb 100644 --- a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Diagnostics { - public sealed class MqttNetLogMessagePublishedEventArgs : EventArgs + public class MqttNetLogMessagePublishedEventArgs : EventArgs { public MqttNetLogMessagePublishedEventArgs(MqttNetLogMessage logMessage) { diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs index 6afeba19..56a664e4 100644 --- a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs +++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Diagnostics { - public sealed class MqttNetLogger : IMqttNetLogger + public class MqttNetLogger : IMqttNetLogger { private readonly string _logId; diff --git a/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs index 86f58b38..df3c4255 100644 --- a/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs +++ b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs @@ -2,10 +2,14 @@ namespace MQTTnet.Exceptions { - public sealed class MqttCommunicationTimedOutException : MqttCommunicationException + public class MqttCommunicationTimedOutException : MqttCommunicationException { - public MqttCommunicationTimedOutException() { } - public MqttCommunicationTimedOutException(Exception innerException) : base(innerException) { } + public MqttCommunicationTimedOutException() + { + } + public MqttCommunicationTimedOutException(Exception innerException) : base(innerException) + { + } } } diff --git a/Frameworks/MQTTnet.NetStandard/Exceptions/MqttProtocolViolationException.cs b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttProtocolViolationException.cs index 8756b3f8..262f4afb 100644 --- a/Frameworks/MQTTnet.NetStandard/Exceptions/MqttProtocolViolationException.cs +++ b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttProtocolViolationException.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Exceptions { - public sealed class MqttProtocolViolationException : Exception + public class MqttProtocolViolationException : Exception { public MqttProtocolViolationException(string message) : base(message) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs index 15f8566e..a325ebe1 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs @@ -14,7 +14,7 @@ using MQTTnet.Client; namespace MQTTnet.Implementations { - public sealed class MqttTcpChannel : IMqttChannel + public class MqttTcpChannel : IMqttChannel { // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs index 40edf367..ab2675a8 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs @@ -13,7 +13,7 @@ using MQTTnet.Client; namespace MQTTnet.Implementations { - public sealed class MqttTcpChannel : IMqttChannel + public class MqttTcpChannel : IMqttChannel { private readonly MqttClientTcpOptions _options; diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs index 733bcdd1..3243cd76 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs @@ -8,7 +8,7 @@ using MQTTnet.Client; namespace MQTTnet.Implementations { - public sealed class MqttWebSocketChannel : IMqttChannel + public class MqttWebSocketChannel : IMqttChannel { private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); private readonly MqttClientWebSocketOptions _options; diff --git a/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs b/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs index 1de1b1a9..87878fa9 100644 --- a/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace MQTTnet.Internal { // From Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-6-asynclock/) - public sealed class AsyncLock : IDisposable + public class AsyncLock : IDisposable { private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly Task _releaser; @@ -30,7 +30,7 @@ namespace MQTTnet.Internal _semaphore?.Dispose(); } - private sealed class Releaser : IDisposable + private class Releaser : IDisposable { private readonly AsyncLock _toRelease; diff --git a/Frameworks/MQTTnet.NetStandard/MqttApplicationMessage.cs b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessage.cs index 387acb21..334dfb40 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttApplicationMessage.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessage.cs @@ -1,23 +1,9 @@ -using System; -using MQTTnet.Protocol; +using MQTTnet.Protocol; namespace MQTTnet { - public sealed class MqttApplicationMessage + public class MqttApplicationMessage { - public MqttApplicationMessage() - { - } - - [Obsolete("Use object initializer or _MqttApplicationMessageBuilder_ instead.")] - public MqttApplicationMessage(string topic, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain) - { - Topic = topic ?? throw new ArgumentNullException(nameof(topic)); - Payload = payload ?? throw new ArgumentNullException(nameof(payload)); - QualityOfServiceLevel = qualityOfServiceLevel; - Retain = retain; - } - public string Topic { get; set; } public byte[] Payload { get; set; } diff --git a/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageReceivedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageReceivedEventArgs.cs index a16a81d1..e81c69a9 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageReceivedEventArgs.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageReceivedEventArgs.cs @@ -2,7 +2,7 @@ namespace MQTTnet { - public sealed class MqttApplicationMessageReceivedEventArgs : EventArgs + public class MqttApplicationMessageReceivedEventArgs : EventArgs { public MqttApplicationMessageReceivedEventArgs(string clientId, MqttApplicationMessage applicationMessage) { diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttConnAckPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttConnAckPacket.cs index 9cf0adfe..dbdeea89 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttConnAckPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttConnAckPacket.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Packets { - public sealed class MqttConnAckPacket : MqttBasePacket + public class MqttConnAckPacket : MqttBasePacket { public bool IsSessionPresent { get; set; } diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttConnectPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttConnectPacket.cs index b1943ced..2134f4bb 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttConnectPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttConnectPacket.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Packets { - public sealed class MqttConnectPacket : MqttBasePacket + public class MqttConnectPacket : MqttBasePacket { public MqttProtocolVersion ProtocolVersion { get; set; } diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttDisconnectPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttDisconnectPacket.cs index 46c3270d..621829a2 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttDisconnectPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttDisconnectPacket.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Packets { - public sealed class MqttDisconnectPacket : MqttBasePacket + public class MqttDisconnectPacket : MqttBasePacket { public override string ToString() { diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPingReqPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPingReqPacket.cs index fee9926c..5c65da1d 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttPingReqPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPingReqPacket.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Packets { - public sealed class MqttPingReqPacket : MqttBasePacket + public class MqttPingReqPacket : MqttBasePacket { public override string ToString() { diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPingRespPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPingRespPacket.cs index c5f82a3b..b6f9bc3c 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttPingRespPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPingRespPacket.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Packets { - public sealed class MqttPingRespPacket : MqttBasePacket + public class MqttPingRespPacket : MqttBasePacket { public override string ToString() { diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubAckPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubAckPacket.cs index e7fe57b7..3015f278 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubAckPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubAckPacket.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Packets { - public sealed class MqttPubAckPacket : MqttBasePublishPacket + public class MqttPubAckPacket : MqttBasePublishPacket { public override string ToString() { diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubCompPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubCompPacket.cs index 98dace70..e9ea18eb 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubCompPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubCompPacket.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Packets { - public sealed class MqttPubCompPacket : MqttBasePublishPacket + public class MqttPubCompPacket : MqttBasePublishPacket { public override string ToString() { diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRecPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRecPacket.cs index 86cd4b45..0184d6b8 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRecPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRecPacket.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Packets { - public sealed class MqttPubRecPacket : MqttBasePublishPacket + public class MqttPubRecPacket : MqttBasePublishPacket { public override string ToString() { diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRelPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRelPacket.cs index ed1d933b..bb37655c 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRelPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRelPacket.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Packets { - public sealed class MqttPubRelPacket : MqttBasePublishPacket + public class MqttPubRelPacket : MqttBasePublishPacket { public override string ToString() { diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPublishPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPublishPacket.cs index 52fcceed..55144a9e 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttPublishPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPublishPacket.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Packets { - public sealed class MqttPublishPacket : MqttBasePublishPacket + public class MqttPublishPacket : MqttBasePublishPacket { public bool Retain { get; set; } diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttSubAckPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttSubAckPacket.cs index 3b8cf7a8..05e18b2b 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttSubAckPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttSubAckPacket.cs @@ -4,7 +4,7 @@ using MQTTnet.Protocol; namespace MQTTnet.Packets { - public sealed class MqttSubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier + public class MqttSubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier { public ushort? PacketIdentifier { get; set; } diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttSubscribePacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttSubscribePacket.cs index 63aede16..60086c47 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttSubscribePacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttSubscribePacket.cs @@ -3,7 +3,7 @@ using System.Linq; namespace MQTTnet.Packets { - public sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier + public class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier { public ushort? PacketIdentifier { get; set; } diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubAckPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubAckPacket.cs index 688195fa..ab5f9f62 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubAckPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubAckPacket.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Packets { - public sealed class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier + public class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier { public ushort? PacketIdentifier { get; set; } diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs index a4d84535..63895f12 100644 --- a/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs +++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Packets { - public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier + public class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier { public ushort? PacketIdentifier { get; set; } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/ByteReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/ByteReader.cs index ec1e3ea2..461bba33 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/ByteReader.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/ByteReader.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Serializer { - public sealed class ByteReader + public class ByteReader { private readonly int _source; private int _index; diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/ByteWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/ByteWriter.cs index af961e1a..9ae21564 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/ByteWriter.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/ByteWriter.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Serializer { - public sealed class ByteWriter + public class ByteWriter { private int _index; private int _byte; diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 369bd16a..8c384e07 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -8,7 +8,7 @@ using MQTTnet.Adapter; namespace MQTTnet.Serializer { - public sealed class MqttPacketSerializer : IMqttPacketSerializer + public class MqttPacketSerializer : IMqttPacketSerializer { public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; diff --git a/Frameworks/MQTTnet.NetStandard/Server/GetOrCreateClientSessionResult.cs b/Frameworks/MQTTnet.NetStandard/Server/GetOrCreateClientSessionResult.cs index 8f5f085e..975d237f 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/GetOrCreateClientSessionResult.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/GetOrCreateClientSessionResult.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Server { - public sealed class GetOrCreateClientSessionResult + public class GetOrCreateClientSessionResult { public bool IsExistingSession { get; set; } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs index 82bcbdf6..a4130889 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs @@ -7,7 +7,7 @@ using MQTTnet.Packets; namespace MQTTnet.Server { - public sealed class MqttClientKeepAliveMonitor + public class MqttClientKeepAliveMonitor { private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch(); private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch(); diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs index 6227e020..97f616c0 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs @@ -11,7 +11,7 @@ using MQTTnet.Protocol; namespace MQTTnet.Server { - public sealed class MqttClientPendingMessagesQueue : IDisposable + public class MqttClientPendingMessagesQueue : IDisposable { private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent(); private readonly IMqttServerOptions _options; diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs index 6e537e9e..0f115534 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs @@ -12,7 +12,7 @@ using MQTTnet.Protocol; namespace MQTTnet.Server { - public sealed class MqttClientSession : IDisposable + public class MqttClientSession : IDisposable { private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index b9ab0136..7918aa8d 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -12,7 +12,7 @@ using MQTTnet.Protocol; namespace MQTTnet.Server { - public sealed class MqttClientSessionsManager : IDisposable + public class MqttClientSessionsManager : IDisposable { private readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); private readonly AsyncLock _sessionPreparationLock = new AsyncLock(); diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs index 6dfa98e3..5f067ff6 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs @@ -7,7 +7,7 @@ using MQTTnet.Protocol; namespace MQTTnet.Server { - public sealed class MqttClientSubscriptionsManager + public class MqttClientSubscriptionsManager { private readonly ConcurrentDictionary _subscriptions = new ConcurrentDictionary(); private readonly IMqttServerOptions _options; diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs index 3ab53cbf..9fe454cf 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs @@ -7,7 +7,7 @@ using MQTTnet.Diagnostics; namespace MQTTnet.Server { - public sealed class MqttRetainedMessagesManager + public class MqttRetainedMessagesManager { private readonly ConcurrentDictionary _messages = new ConcurrentDictionary(); private readonly IMqttNetChildLogger _logger; diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerDefaultEndpointOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerDefaultEndpointOptions.cs index 545499b5..0965104b 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerDefaultEndpointOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerDefaultEndpointOptions.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Server { - public sealed class MqttServerDefaultEndpointOptions + public class MqttServerDefaultEndpointOptions { public bool IsEnabled { get; set; } = true; diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsEndpointOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsEndpointOptions.cs index 73be93c5..aaf4948b 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsEndpointOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsEndpointOptions.cs @@ -2,7 +2,7 @@ namespace MQTTnet.Server { - public sealed class MqttServerTlsEndpointOptions + public class MqttServerTlsEndpointOptions { public bool IsEnabled { get; set; } diff --git a/Frameworks/MQTTnet.NetStandard/TopicFilter.cs b/Frameworks/MQTTnet.NetStandard/TopicFilter.cs index 1dad73d0..7eebbc2c 100644 --- a/Frameworks/MQTTnet.NetStandard/TopicFilter.cs +++ b/Frameworks/MQTTnet.NetStandard/TopicFilter.cs @@ -2,7 +2,7 @@ namespace MQTTnet { - public sealed class TopicFilter + public class TopicFilter { public TopicFilter(string topic, MqttQualityOfServiceLevel qualityOfServiceLevel) { From 09aacf011eab15a82aa5af523fab6d67c7cfc525 Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Thu, 31 May 2018 07:59:25 +0200 Subject: [PATCH 067/138] use new project system for benchmarks --- Tests/MQTTnet.Benchmarks/App.config | 50 +---- .../MQTTnet.Benchmarks.csproj | 173 +----------------- .../Properties/AssemblyInfo.cs | 36 ---- 3 files changed, 10 insertions(+), 249 deletions(-) delete mode 100644 Tests/MQTTnet.Benchmarks/Properties/AssemblyInfo.cs diff --git a/Tests/MQTTnet.Benchmarks/App.config b/Tests/MQTTnet.Benchmarks/App.config index 5a41bc2c..662f695c 100644 --- a/Tests/MQTTnet.Benchmarks/App.config +++ b/Tests/MQTTnet.Benchmarks/App.config @@ -1,54 +1,6 @@  - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index 8d3a7c89..b5243afe 100644 --- a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -1,172 +1,17 @@ - - - + + - Debug - AnyCPU - {998D04DD-7CB0-45F5-A393-E2495C16399E} Exe - MQTTnet.Benchmarks - MQTTnet.Benchmarks - v4.6.2 - 512 - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 + Full + net461 + - - ..\..\packages\BenchmarkDotNet.0.10.14\lib\net46\BenchmarkDotNet.dll - - - ..\..\packages\BenchmarkDotNet.Core.0.10.14\lib\net46\BenchmarkDotNet.Core.dll - - - ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.14\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll - - - ..\..\packages\Microsoft.CodeAnalysis.Common.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.dll - - - ..\..\packages\Microsoft.CodeAnalysis.CSharp.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll - - - ..\..\packages\Microsoft.DotNet.InternalAbstractions.1.0.0\lib\net451\Microsoft.DotNet.InternalAbstractions.dll - - - ..\..\packages\Microsoft.DotNet.PlatformAbstractions.1.1.1\lib\net451\Microsoft.DotNet.PlatformAbstractions.dll - - - ..\..\packages\Microsoft.Win32.Registry.4.3.0\lib\net46\Microsoft.Win32.Registry.dll - - - - ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll - True - - - ..\..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - True - - - - ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll - - - - ..\..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll - - - ..\..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll - - - ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll - True - - - ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll - - - ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll - - - - - ..\..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll - - - ..\..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll - - - ..\..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll - - - ..\..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll - - - ..\..\packages\System.Runtime.InteropServices.4.3.0\lib\net462\System.Runtime.InteropServices.dll - - - ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll - - - ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - - - ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - - - ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - - - ..\..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll - - - ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - - - ..\..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll - - - ..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll - - - - - - - - - ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll - - - ..\..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll - - - ..\..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll - - - ..\..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll - + + - - - - - - + - - - - - - - - - - - {3587E506-55A2-4EB3-99C7-DC01E42D25D2} - MQTTnet.NetStandard - - - + \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/Properties/AssemblyInfo.cs b/Tests/MQTTnet.Benchmarks/Properties/AssemblyInfo.cs deleted file mode 100644 index 4df83f6e..00000000 --- a/Tests/MQTTnet.Benchmarks/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("MQTTnet.Benchmarks")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("MQTTnet.Benchmarks")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("998d04dd-7cb0-45f5-a393-e2495c16399e")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] From 4e74b59796cd86c998233f083bf4f17572fc4c3b Mon Sep 17 00:00:00 2001 From: JanEggers Date: Fri, 6 Apr 2018 08:44:50 +0200 Subject: [PATCH 068/138] added extension method --- .../ApplicationMessagePublisherExtensions.cs | 21 ++++++++++++++++--- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 13 ++++++------ .../ManagedClientTest.cs | 6 +++--- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/ApplicationMessagePublisherExtensions.cs b/Frameworks/MQTTnet.NetStandard/ApplicationMessagePublisherExtensions.cs index b1bf62f5..4479f22d 100644 --- a/Frameworks/MQTTnet.NetStandard/ApplicationMessagePublisherExtensions.cs +++ b/Frameworks/MQTTnet.NetStandard/ApplicationMessagePublisherExtensions.cs @@ -19,7 +19,9 @@ namespace MQTTnet if (publisher == null) throw new ArgumentNullException(nameof(publisher)); if (topic == null) throw new ArgumentNullException(nameof(topic)); - return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).Build()); + return publisher.PublishAsync(builder => builder + .WithTopic(topic) + ); } public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload) @@ -27,7 +29,10 @@ namespace MQTTnet if (publisher == null) throw new ArgumentNullException(nameof(publisher)); if (topic == null) throw new ArgumentNullException(nameof(topic)); - return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).Build()); + return publisher.PublishAsync(builder => builder + .WithTopic(topic) + .WithPayload(payload) + ); } public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) @@ -35,7 +40,17 @@ namespace MQTTnet if (publisher == null) throw new ArgumentNullException(nameof(publisher)); if (topic == null) throw new ArgumentNullException(nameof(topic)); - return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); + return publisher.PublishAsync(builder => builder + .WithTopic(topic) + .WithPayload(payload) + .WithQualityOfServiceLevel(qualityOfServiceLevel) + ); + } + + public static Task PublishAsync(this IApplicationMessagePublisher publisher, Func builder) + { + var message = builder(new MqttApplicationMessageBuilder()).Build(); + return publisher.PublishAsync(message); } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index 6fe27530..7c4faf0b 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -214,7 +214,7 @@ namespace MQTTnet.Core.Tests await s.StartAsync(new MqttServerOptions()); var c1 = await serverAdapter.ConnectTestClient("c1"); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).Build()); + await c1.PublishAsync(builder => builder.WithTopic("retained").WithPayload(new byte[3])); await c1.DisconnectAsync(); var c2 = await serverAdapter.ConnectTestClient("c2"); @@ -272,8 +272,8 @@ namespace MQTTnet.Core.Tests await s.StartAsync(new MqttServerOptions()); var c1 = await serverAdapter.ConnectTestClient("c1"); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build()); + await c1.PublishAsync(builder => builder.WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag()); + await c1.PublishAsync(builder => builder.WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag()); await c1.DisconnectAsync(); var c2 = await serverAdapter.ConnectTestClient("c2"); @@ -366,8 +366,7 @@ namespace MQTTnet.Core.Tests isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(args.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; }; - var m = new MqttApplicationMessageBuilder().WithTopic("test").Build(); - await c1.PublishAsync(m); + await c1.PublishAsync(builder => builder.WithTopic("test")); await c1.DisconnectAsync(); await Task.Delay(500); @@ -403,7 +402,7 @@ namespace MQTTnet.Core.Tests }; await c1.SubscribeAsync("A", MqttQualityOfServiceLevel.AtMostOnce); - await c2.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("A").WithPayload(Encoding.UTF8.GetBytes("The body")).Build()); + await c2.PublishAsync(builder => builder.WithTopic("A").WithPayload(Encoding.UTF8.GetBytes("The body"))); await Task.Delay(1000); } @@ -452,7 +451,7 @@ namespace MQTTnet.Core.Tests c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic(topicFilter).WithQualityOfServiceLevel(filterQualityOfServiceLevel).Build()); - await c2.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(new byte[0]).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); + await c2.PublishAsync(builder => builder.WithTopic(topic).WithPayload(new byte[0]).WithQualityOfServiceLevel(qualityOfServiceLevel)); await Task.Delay(500); await c1.UnsubscribeAsync(topicFilter); diff --git a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs index e23e9945..4ecca0ff 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs @@ -39,15 +39,15 @@ namespace MQTTnet.TestApp.NetCore Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic); }; - await managedClient.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("Step").WithPayload("1").Build()); - await managedClient.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("Step").WithPayload("2").WithAtLeastOnceQoS().Build()); + await managedClient.PublishAsync(builder => builder.WithTopic("Step").WithPayload("1")); + await managedClient.PublishAsync(builder => builder.WithTopic("Step").WithPayload("2").WithAtLeastOnceQoS()); await managedClient.StartAsync(options); await managedClient.SubscribeAsync(new TopicFilter("xyz", MqttQualityOfServiceLevel.AtMostOnce)); await managedClient.SubscribeAsync(new TopicFilter("abc", MqttQualityOfServiceLevel.AtMostOnce)); - await managedClient.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("Step").WithPayload("3").Build()); + await managedClient.PublishAsync(builder => builder.WithTopic("Step").WithPayload("3")); Console.WriteLine("Managed client started."); Console.ReadLine(); From 0505efbac1e91bb2962c6af11de046c13795333c Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 31 May 2018 21:34:51 +0200 Subject: [PATCH 069/138] Refactor graceful socket close. --- .../Adapter/MqttChannelAdapter.cs | 15 ++++------- .../Internal/ExceptionHelper.cs | 12 +++++++++ .../Serializer/MqttPacketReader.cs | 26 ++++++++----------- 3 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 Frameworks/MQTTnet.NetStandard/Internal/ExceptionHelper.cs diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 7b8f08a9..92c8f881 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using MQTTnet.Channel; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; +using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Serializer; @@ -133,18 +134,14 @@ namespace MQTTnet.Adapter private async Task ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken) { var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, cancellationToken).ConfigureAwait(false); - if (!fixedHeader.HasValue) - { - return null; - } - + ReadingPacketStarted?.Invoke(this, EventArgs.Empty); try { var bodyLength = await MqttPacketReader.ReadBodyLengthAsync(channel, cancellationToken).ConfigureAwait(false); if (bodyLength == 0) { - return new ReceivedMqttPacket(fixedHeader.Value, null); + return new ReceivedMqttPacket(fixedHeader, null); } var body = new MemoryStream(bodyLength); @@ -159,11 +156,9 @@ namespace MQTTnet.Adapter } var readBytesCount = await channel.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false); - - // Check if the client closed the connection before sending the full body. if (readBytesCount <= 0) { - throw new MqttCommunicationException("Connection closed while reading remaining packet body."); + ExceptionHelper.ThrowGracefulSocketClose(); } // Here is no need to await because internally only an array is used and no real I/O operation is made. @@ -173,7 +168,7 @@ namespace MQTTnet.Adapter body.Seek(0L, SeekOrigin.Begin); - return new ReceivedMqttPacket(fixedHeader.Value, body); + return new ReceivedMqttPacket(fixedHeader, body); } finally { diff --git a/Frameworks/MQTTnet.NetStandard/Internal/ExceptionHelper.cs b/Frameworks/MQTTnet.NetStandard/Internal/ExceptionHelper.cs new file mode 100644 index 00000000..2b504511 --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Internal/ExceptionHelper.cs @@ -0,0 +1,12 @@ +using MQTTnet.Exceptions; + +namespace MQTTnet.Internal +{ + public static class ExceptionHelper + { + public static void ThrowGracefulSocketClose() + { + throw new MqttCommunicationException("Connection gracefully closed from the remote party."); + } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs index 432dc90e..4b3b6b17 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs @@ -5,18 +5,14 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Channel; using MQTTnet.Exceptions; +using MQTTnet.Internal; namespace MQTTnet.Serializer { public static class MqttPacketReader { - public static async Task ReadFixedHeaderAsync(IMqttChannel channel, CancellationToken cancellationToken) + public static async Task ReadFixedHeaderAsync(IMqttChannel channel, CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested) - { - return null; - } - // Wait for the next package which starts with the header. At this point there will probably // some large delay and thus the thread should be put back to the pool (await). So ReadByte() // is not an option here. @@ -24,7 +20,7 @@ namespace MQTTnet.Serializer var readCount = await channel.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); if (readCount <= 0) { - return null; + ExceptionHelper.ThrowGracefulSocketClose(); } return buffer[0]; @@ -68,13 +64,6 @@ namespace MQTTnet.Serializer return stream.ReadBytes((int)(stream.Length - stream.Position)); } - public static byte[] ReadBytes(this Stream stream, int count) - { - var buffer = new byte[count]; - stream.Read(buffer, 0, count); - return buffer; - } - public static async Task ReadBodyLengthAsync(IMqttChannel channel, 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. @@ -93,7 +82,7 @@ namespace MQTTnet.Serializer var readCount = await channel.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); if (readCount <= 0) { - throw new MqttCommunicationException("Connection closed while reading remaining length data."); + ExceptionHelper.ThrowGracefulSocketClose(); } encodedByte = buffer[0]; @@ -109,5 +98,12 @@ namespace MQTTnet.Serializer return value; } + + private static byte[] ReadBytes(this Stream stream, int count) + { + var buffer = new byte[count]; + stream.Read(buffer, 0, count); + return buffer; + } } } From 1cbec27b7f34269474d09bd0d048889c3454dc6a Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 31 May 2018 21:48:09 +0200 Subject: [PATCH 070/138] Fix projects --- .../MQTTnet.Extensions.Rpc.csproj | 2 +- .../MQTTnet.AspnetCore.csproj | 2 +- MQTTnet.sln | 4 +-- .../MQTTnet.Benchmarks.csproj | 2 +- .../MQTTnet.Core.Tests.csproj | 2 +- .../MQTTnet.TestApp.NetCore.csproj | 2 +- .../MainPage.xaml | 31 ++++++++++++++++++- .../MainPage.xaml.cs | 8 +++++ 8 files changed, 45 insertions(+), 8 deletions(-) diff --git a/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj b/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj index fa915cf4..64bbf92c 100644 --- a/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj +++ b/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj @@ -25,7 +25,7 @@ - + diff --git a/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj b/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj index a19f2b3b..d3f06e82 100644 --- a/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj +++ b/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj @@ -22,7 +22,7 @@ - + diff --git a/MQTTnet.sln b/MQTTnet.sln index b60e21d5..6cb185db 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -11,7 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp.UniversalWindows", "Tests\MQTTnet.TestApp.UniversalWindows\MQTTnet.TestApp.UniversalWindows.csproj", "{FF1F72D6-9524-4422-9497-3CC0002216ED}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.NetStandard", "Frameworks\MQTTnet.NetStandard\MQTTnet.Netstandard.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.NetStandard", "Frameworks\MQTTnet.NetStandard\MQTTnet.NetStandard.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1-BC3A-420A-BE9C-FA2401431CF9}" ProjectSection(SolutionItems) = preProject @@ -38,7 +38,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Benchmarks", "Tests\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{998D04DD-7CB0-45F5-A393-E2495C16399E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Tests\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{998D04DD-7CB0-45F5-A393-E2495C16399E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index b5243afe..72af0930 100644 --- a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -11,7 +11,7 @@ - + \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj index b03d0d11..d3ada44d 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj +++ b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj index 1784cc0e..43fd8c87 100644 --- a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj +++ b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj @@ -15,7 +15,7 @@ - + diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml index 3698613f..906d9b8a 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml @@ -43,8 +43,8 @@ - + Topic: @@ -69,6 +69,7 @@ + Method: @@ -103,6 +104,7 @@ + Topic: @@ -132,6 +134,7 @@ + Port: @@ -151,6 +154,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -159,3 +187,4 @@ + diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 3de538ad..52db69e3 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -372,6 +372,14 @@ namespace MQTTnet.TestApp.UniversalWindows RpcResponses.Items.Clear(); } + private void ClearSessions(object sender, RoutedEventArgs e) + { + } + + private void RefreshSessions(object sender, RoutedEventArgs e) + { + } + private async Task WikiCode() { { From fd50e4e26022f2e797d776870b64bd28cf64ff56 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Thu, 31 May 2018 22:10:04 +0200 Subject: [PATCH 071/138] more benchmarks --- .../ChannelAdapterBenchmark.cs | 107 ++++++++++++++++++ .../MqttTcpChannelBenchmark.cs | 52 +++++++++ Tests/MQTTnet.Benchmarks/Program.cs | 8 ++ 3 files changed, 167 insertions(+) create mode 100644 Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs create mode 100644 Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs diff --git a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs new file mode 100644 index 00000000..5f66e6bc --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -0,0 +1,107 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Exporters; +using BenchmarkDotNet.Attributes.Jobs; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Validators; +using MQTTnet.Adapter; +using MQTTnet.Core.Internal; +using MQTTnet.Diagnostics; +using MQTTnet.Internal; +using MQTTnet.Packets; +using MQTTnet.Serializer; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace MQTTnet.Benchmarks +{ + public class AllowNonOptimized : ManualConfig + { + public AllowNonOptimized() + { + Add(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS + Add(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default + Add(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default + Add(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default + Add(Job.InProcess); + } + } + + [MemoryDiagnoser] + public class ChannelAdapterBenchmark + { + private MqttChannelAdapter _channelAdapter; + private int _iterations; + private MemoryStream _stream; + private MqttPublishPacket _packet; + + [GlobalSetup] + public void Setup() + { + var message = new MqttApplicationMessageBuilder() + .WithTopic("A") + .Build(); + + _packet = message.ToPublishPacket(); + var serializer = new MqttPacketSerializer(); + var serializedPacket = Join(serializer.Serialize(_packet)); + + _iterations = 10000; + + _stream = new MemoryStream(_iterations * serializedPacket.Length); + + for (var i = 0; i < _iterations; i++) + { + _stream.Write(serializedPacket, 0, serializedPacket.Length); + } + + _stream.Position = 0; + + var channel = new TestMqttChannel(_stream); + + _channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetLogger().CreateChildLogger(nameof(MqttChannelAdapter))); + } + + [Benchmark] + public void Receive_10000_Messages() + { + _stream.Position = 0; + + for (var i = 0; i < 10000; i++) + { + _channelAdapter.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None).GetAwaiter().GetResult(); + } + + _stream.Position = 0; + } + + [Benchmark] + public void Send_10000_Messages() + { + _stream.Position = 0; + + for (var i = 0; i < 10000; i++) + { + _channelAdapter.SendPacketsAsync(TimeSpan.FromSeconds(15), new[] { _packet }, CancellationToken.None).GetAwaiter().GetResult(); + } + + _stream.Position = 0; + } + + private static byte[] Join(params ArraySegment[] chunks) + { + var buffer = new MemoryStream(); + foreach (var chunk in chunks) + { + buffer.Write(chunk.Array, chunk.Offset, chunk.Count); + } + + return buffer.ToArray(); + } + } +} diff --git a/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs b/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs new file mode 100644 index 00000000..6709ff79 --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs @@ -0,0 +1,52 @@ +using BenchmarkDotNet.Attributes; +using MQTTnet.Adapter; +using MQTTnet.Channel; +using MQTTnet.Client; +using MQTTnet.Diagnostics; +using MQTTnet.Implementations; +using MQTTnet.Server; +using System.Threading; + +namespace MQTTnet.Benchmarks +{ + [MemoryDiagnoser] + public class MqttTcpChannelBenchmark + { + private IMqttServer _mqttServer; + private IMqttChannel _clientChannel; + private IMqttChannel _serverChannel; + + [GlobalSetup] + public void Setup() + { + var factory = new MqttFactory(); + var tcpServer = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger()); + tcpServer.ClientAccepted += (sender, args) => _serverChannel = (IMqttChannel)args.Client.GetType().GetField("_channel", System.Reflection.BindingFlags.NonPublic| System.Reflection.BindingFlags.Instance).GetValue(args.Client); + + _mqttServer = factory.CreateMqttServer(new[] { tcpServer }, new MqttNetLogger()); + + var serverOptions = new MqttServerOptionsBuilder().Build(); + _mqttServer.StartAsync(serverOptions).GetAwaiter().GetResult(); + + var clientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("localhost").Build(); + + _clientChannel = new MqttTcpChannel((MqttClientTcpOptions)clientOptions.ChannelOptions); + + _clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult(); + } + + [Benchmark] + public void Send_10000_Chunks() + { + var size = 5; + var iterations = 10000; + for (var i = 0; i < iterations; i++) + { + _serverChannel.WriteAsync(new byte[size], 0, size, CancellationToken.None).GetAwaiter().GetResult(); + _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).GetAwaiter().GetResult(); + } + + } + } +} diff --git a/Tests/MQTTnet.Benchmarks/Program.cs b/Tests/MQTTnet.Benchmarks/Program.cs index a65de421..e643e97b 100644 --- a/Tests/MQTTnet.Benchmarks/Program.cs +++ b/Tests/MQTTnet.Benchmarks/Program.cs @@ -13,6 +13,8 @@ namespace MQTTnet.Benchmarks Console.WriteLine("2 = SerializerBenchmark"); Console.WriteLine("3 = LoggerBenchmark"); Console.WriteLine("4 = TopicFilterComparerBenchmark"); + Console.WriteLine("5 = ChannelAdapterBenchmark"); + Console.WriteLine("6 = MqttTcpChannelBenchmark"); var pressedKey = Console.ReadKey(true); switch (pressedKey.KeyChar) @@ -29,6 +31,12 @@ namespace MQTTnet.Benchmarks case '4': BenchmarkRunner.Run(); break; + case '5': + BenchmarkRunner.Run(); + break; + case '6': + BenchmarkRunner.Run(); + break; } Console.ReadLine(); From cadf9f797f608a4a8f407164077d9d197580adcf Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Fri, 1 Jun 2018 08:47:00 +0200 Subject: [PATCH 072/138] added pipe based benchmak --- MQTTnet.sln | 3 + .../MQTTnet.Benchmarks.csproj | 2 + .../MqttTcpChannelBenchmark.cs | 39 ++- Tests/MQTTnet.Benchmarks/Program.cs | 4 + .../Tcp/BufferExtensions.cs | 24 ++ Tests/MQTTnet.Benchmarks/Tcp/DuplexPipe.cs | 41 +++ .../MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs | 71 +++++ .../MQTTnet.Benchmarks/Tcp/SocketReceiver.cs | 39 +++ Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs | 99 +++++++ Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs | 247 ++++++++++++++++++ Tests/MQTTnet.Benchmarks/TcpPipesBenchmark.cs | 72 +++++ 11 files changed, 633 insertions(+), 8 deletions(-) create mode 100644 Tests/MQTTnet.Benchmarks/Tcp/BufferExtensions.cs create mode 100644 Tests/MQTTnet.Benchmarks/Tcp/DuplexPipe.cs create mode 100644 Tests/MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs create mode 100644 Tests/MQTTnet.Benchmarks/Tcp/SocketReceiver.cs create mode 100644 Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs create mode 100644 Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs create mode 100644 Tests/MQTTnet.Benchmarks/TcpPipesBenchmark.cs diff --git a/MQTTnet.sln b/MQTTnet.sln index 6cb185db..a8370c66 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -41,6 +41,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Tests\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{998D04DD-7CB0-45F5-A393-E2495C16399E}" EndProject Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM diff --git a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index 72af0930..ad3aa55a 100644 --- a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -4,10 +4,12 @@ Exe Full net461 + 7.2 + diff --git a/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs b/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs index 6709ff79..740b83ee 100644 --- a/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs @@ -1,11 +1,11 @@ using BenchmarkDotNet.Attributes; -using MQTTnet.Adapter; using MQTTnet.Channel; using MQTTnet.Client; using MQTTnet.Diagnostics; using MQTTnet.Implementations; using MQTTnet.Server; using System.Threading; +using System.Threading.Tasks; namespace MQTTnet.Benchmarks { @@ -13,8 +13,10 @@ namespace MQTTnet.Benchmarks public class MqttTcpChannelBenchmark { private IMqttServer _mqttServer; - private IMqttChannel _clientChannel; private IMqttChannel _serverChannel; + + + private IMqttChannel _clientChannel; [GlobalSetup] public void Setup() @@ -31,22 +33,43 @@ namespace MQTTnet.Benchmarks var clientOptions = new MqttClientOptionsBuilder() .WithTcpServer("localhost").Build(); - _clientChannel = new MqttTcpChannel((MqttClientTcpOptions)clientOptions.ChannelOptions); + var tcpOptions = (MqttClientTcpOptions) clientOptions.ChannelOptions; + _clientChannel = new MqttTcpChannel(tcpOptions); _clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult(); } - + [Benchmark] - public void Send_10000_Chunks() + public async Task Send_10000_Chunks() { var size = 5; var iterations = 10000; + + await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size)); + } + + private async Task ReadAsync(int iterations, int size) + { + await Task.Yield(); + + var expected = iterations * size; + long read = 0; + + while (read < expected) + { + var readresult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false); + read += readresult; + } + } + + private async Task WriteAsync(int iterations, int size) + { + await Task.Yield(); + for (var i = 0; i < iterations; i++) { - _serverChannel.WriteAsync(new byte[size], 0, size, CancellationToken.None).GetAwaiter().GetResult(); - _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).GetAwaiter().GetResult(); + await _serverChannel.WriteAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false); } - } } } diff --git a/Tests/MQTTnet.Benchmarks/Program.cs b/Tests/MQTTnet.Benchmarks/Program.cs index e643e97b..53069163 100644 --- a/Tests/MQTTnet.Benchmarks/Program.cs +++ b/Tests/MQTTnet.Benchmarks/Program.cs @@ -15,6 +15,7 @@ namespace MQTTnet.Benchmarks Console.WriteLine("4 = TopicFilterComparerBenchmark"); Console.WriteLine("5 = ChannelAdapterBenchmark"); Console.WriteLine("6 = MqttTcpChannelBenchmark"); + Console.WriteLine("7 = TcpPipesBenchmark"); var pressedKey = Console.ReadKey(true); switch (pressedKey.KeyChar) @@ -37,6 +38,9 @@ namespace MQTTnet.Benchmarks case '6': BenchmarkRunner.Run(); break; + case '7': + BenchmarkRunner.Run(); + break; } Console.ReadLine(); diff --git a/Tests/MQTTnet.Benchmarks/Tcp/BufferExtensions.cs b/Tests/MQTTnet.Benchmarks/Tcp/BufferExtensions.cs new file mode 100644 index 00000000..0a2c19e8 --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/Tcp/BufferExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Playground.Client.Mqtt.Tcp +{ + public static class BufferExtensions + { + public static ArraySegment GetArray(this Memory memory) + { + return ((ReadOnlyMemory)memory).GetArray(); + } + + public static ArraySegment GetArray(this ReadOnlyMemory memory) + { + if (!MemoryMarshal.TryGetArray(memory, out var result)) + { + throw new InvalidOperationException("Buffer backed by array was expected"); + } + return result; + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/Tcp/DuplexPipe.cs b/Tests/MQTTnet.Benchmarks/Tcp/DuplexPipe.cs new file mode 100644 index 00000000..f5f3316d --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/Tcp/DuplexPipe.cs @@ -0,0 +1,41 @@ +using System.IO.Pipelines; + +namespace MQTTnet.Benchmarks.Tcp +{ + public class DuplexPipe : IDuplexPipe + { + public DuplexPipe(PipeReader reader, PipeWriter writer) + { + Input = reader; + Output = writer; + } + + public PipeReader Input { get; } + + public PipeWriter Output { get; } + + public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions) + { + var input = new Pipe(inputOptions); + var output = new Pipe(outputOptions); + + var transportToApplication = new DuplexPipe(output.Reader, input.Writer); + var applicationToTransport = new DuplexPipe(input.Reader, output.Writer); + + return new DuplexPipePair(applicationToTransport, transportToApplication); + } + + // This class exists to work around issues with value tuple on .NET Framework + public readonly struct DuplexPipePair + { + public IDuplexPipe Transport { get; } + public IDuplexPipe Application { get; } + + public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application) + { + Transport = transport; + Application = application; + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs b/Tests/MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs new file mode 100644 index 00000000..dcaaaf41 --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.Pipelines; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Playground.Client.Mqtt.Tcp +{ + public class SocketAwaitable : ICriticalNotifyCompletion + { + private static readonly Action _callbackCompleted = () => { }; + + private readonly PipeScheduler _ioScheduler; + + private Action _callback; + private int _bytesTransferred; + private SocketError _error; + + public SocketAwaitable(PipeScheduler ioScheduler) + { + _ioScheduler = ioScheduler; + } + + public SocketAwaitable GetAwaiter() => this; + public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted); + + public int GetResult() + { + Debug.Assert(ReferenceEquals(_callback, _callbackCompleted)); + + _callback = null; + + if (_error != SocketError.Success) + { + throw new SocketException((int)_error); + } + + return _bytesTransferred; + } + + public void OnCompleted(Action continuation) + { + if (ReferenceEquals(_callback, _callbackCompleted) || + ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted)) + { + Task.Run(continuation); + } + } + + public void UnsafeOnCompleted(Action continuation) + { + OnCompleted(continuation); + } + + public void Complete(int bytesTransferred, SocketError socketError) + { + _error = socketError; + _bytesTransferred = bytesTransferred; + var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); + + if (continuation != null) + { + _ioScheduler.Schedule(state => ((Action)state)(), continuation); + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/Tcp/SocketReceiver.cs b/Tests/MQTTnet.Benchmarks/Tcp/SocketReceiver.cs new file mode 100644 index 00000000..e2f19c07 --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/Tcp/SocketReceiver.cs @@ -0,0 +1,39 @@ +using System; +using System.IO.Pipelines; +using System.Net.Sockets; +using Playground.Client.Mqtt.Tcp; + +namespace MQTTnet.Benchmarks.Tcp +{ + public class SocketReceiver + { + private readonly Socket _socket; + private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs(); + private readonly SocketAwaitable _awaitable; + + public SocketReceiver(Socket socket, PipeScheduler scheduler) + { + _socket = socket; + _awaitable = new SocketAwaitable(scheduler); + _eventArgs.UserToken = _awaitable; + _eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError); + } + + public SocketAwaitable ReceiveAsync(Memory buffer) + { +#if NETCOREAPP2_1 + _eventArgs.SetBuffer(buffer); +#else + var segment = buffer.GetArray(); + + _eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); +#endif + if (!_socket.ReceiveAsync(_eventArgs)) + { + _awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError); + } + + return _awaitable; + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs b/Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs new file mode 100644 index 00000000..475fa685 --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs @@ -0,0 +1,99 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.Pipelines; +using System.Net.Sockets; +using Playground.Client.Mqtt.Tcp; + +namespace MQTTnet.Benchmarks.Tcp +{ + public class SocketSender + { + private readonly Socket _socket; + private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs(); + private readonly SocketAwaitable _awaitable; + + private List> _bufferList; + + public SocketSender(Socket socket, PipeScheduler scheduler) + { + _socket = socket; + _awaitable = new SocketAwaitable(scheduler); + _eventArgs.UserToken = _awaitable; + _eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError); + } + + public SocketAwaitable SendAsync(in ReadOnlySequence buffers) + { + if (buffers.IsSingleSegment) + { + return SendAsync(buffers.First); + } + +#if NETCOREAPP2_1 + if (!_eventArgs.MemoryBuffer.Equals(Memory.Empty)) +#else + if (_eventArgs.Buffer != null) +#endif + { + _eventArgs.SetBuffer(null, 0, 0); + } + + _eventArgs.BufferList = GetBufferList(buffers); + + if (!_socket.SendAsync(_eventArgs)) + { + _awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError); + } + + return _awaitable; + } + + private SocketAwaitable SendAsync(ReadOnlyMemory memory) + { + // The BufferList getter is much less expensive then the setter. + if (_eventArgs.BufferList != null) + { + _eventArgs.BufferList = null; + } + +#if NETCOREAPP2_1 + _eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory)); +#else + var segment = memory.GetArray(); + + _eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); +#endif + if (!_socket.SendAsync(_eventArgs)) + { + _awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError); + } + + return _awaitable; + } + + private List> GetBufferList(in ReadOnlySequence buffer) + { + Debug.Assert(!buffer.IsEmpty); + Debug.Assert(!buffer.IsSingleSegment); + + if (_bufferList == null) + { + _bufferList = new List>(); + } + else + { + // Buffers are pooled, so it's OK to root them until the next multi-buffer write. + _bufferList.Clear(); + } + + foreach (var b in buffer) + { + _bufferList.Add(b.GetArray()); + } + + return _bufferList; + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs b/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs new file mode 100644 index 00000000..e73067c7 --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs @@ -0,0 +1,247 @@ +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 MQTTnet.Exceptions; +using Playground.Client.Mqtt.Tcp; + +namespace MQTTnet.Benchmarks.Tcp +{ + public class TcpConnection + { + private readonly Socket _socket; + private volatile bool _aborted; + private readonly EndPoint _endPoint; + private IDuplexPipe _application; + private IDuplexPipe _transport; + private readonly SocketSender _sender; + private readonly SocketReceiver _receiver; + + public TcpConnection(EndPoint endPoint) + { + _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + _endPoint = endPoint; + + _sender = new SocketSender(_socket, PipeScheduler.ThreadPool); + _receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool); + } + + public TcpConnection(Socket socket) + { + _socket = socket; + _endPoint = socket.RemoteEndPoint; + + _sender = new SocketSender(_socket, PipeScheduler.ThreadPool); + _receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool); + } + + public Task DisposeAsync() + { + _transport?.Output.Complete(); + _transport?.Input.Complete(); + + _socket?.Dispose(); + + return Task.CompletedTask; + } + + public async Task StartAsync() + { + if (!_socket.Connected) + { + await _socket.ConnectAsync(_endPoint); + } + + var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default); + + _transport = pair.Transport; + _application = pair.Application; + + _ = ExecuteAsync(); + + return pair.Transport; + } + + private async Task ExecuteAsync() + { + Exception sendError = null; + try + { + // Spawn send and receive logic + var receiveTask = DoReceive(); + var sendTask = DoSend(); + + // If the sending task completes then close the receive + // We don't need to do this in the other direction because the kestrel + // will trigger the output closing once the input is complete. + if (await Task.WhenAny(receiveTask, sendTask) == sendTask) + { + // Tell the reader it's being aborted + _socket.Dispose(); + } + + // Now wait for both to complete + await receiveTask; + sendError = await sendTask; + + // Dispose the socket(should noop if already called) + _socket.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"Unexpected exception in {nameof(TcpConnection)}.{nameof(StartAsync)}: " + ex); + } + finally + { + // Complete the output after disposing the socket + _application.Input.Complete(sendError); + } + } + private async Task DoReceive() + { + Exception error = null; + + try + { + await ProcessReceives(); + } + catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset) + { + error = new MqttCommunicationException(ex); + } + catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted || + ex.SocketErrorCode == SocketError.ConnectionAborted || + ex.SocketErrorCode == SocketError.Interrupted || + ex.SocketErrorCode == SocketError.InvalidArgument) + { + if (!_aborted) + { + // Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix. + //error = new MqttCommunicationException(); + } + } + catch (ObjectDisposedException) + { + if (!_aborted) + { + //error = new MqttCommunicationException(); + } + } + catch (IOException ex) + { + error = ex; + } + catch (Exception ex) + { + error = new IOException(ex.Message, ex); + } + finally + { + if (_aborted) + { + //error = error ?? new MqttCommunicationException(); + } + + _application.Output.Complete(error); + } + } + + private async Task ProcessReceives() + { + while (true) + { + // Ensure we have some reasonable amount of buffer space + var buffer = _application.Output.GetMemory(); + + var bytesReceived = await _receiver.ReceiveAsync(buffer); + + if (bytesReceived == 0) + { + // FIN + break; + } + + _application.Output.Advance(bytesReceived); + + var flushTask = _application.Output.FlushAsync(); + + if (!flushTask.IsCompleted) + { + await flushTask; + } + + var result = flushTask.GetAwaiter().GetResult(); + if (result.IsCompleted) + { + // Pipe consumer is shut down, do we stop writing + break; + } + } + } + + private async Task DoSend() + { + Exception error = null; + + try + { + await ProcessSends(); + } + catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted) + { + error = null; + } + catch (ObjectDisposedException) + { + error = null; + } + catch (IOException ex) + { + error = ex; + } + catch (Exception ex) + { + error = new IOException(ex.Message, ex); + } + finally + { + _aborted = true; + _socket.Shutdown(SocketShutdown.Both); + } + + return error; + } + + private async Task ProcessSends() + { + while (true) + { + // Wait for data to write from the pipe producer + var result = await _application.Input.ReadAsync(); + var buffer = result.Buffer; + + if (result.IsCanceled) + { + break; + } + + var end = buffer.End; + var isCompleted = result.IsCompleted; + if (!buffer.IsEmpty) + { + await _sender.SendAsync(buffer); + } + + _application.Input.AdvanceTo(end); + + if (isCompleted) + { + break; + } + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/TcpPipesBenchmark.cs b/Tests/MQTTnet.Benchmarks/TcpPipesBenchmark.cs new file mode 100644 index 00000000..cb3ffbaf --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/TcpPipesBenchmark.cs @@ -0,0 +1,72 @@ +using System.IO.Pipelines; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using MQTTnet.Benchmarks.Tcp; + +namespace MQTTnet.Benchmarks +{ + [MemoryDiagnoser] + public class TcpPipesBenchmark + { + private IDuplexPipe _client; + private IDuplexPipe _server; + + [GlobalSetup] + public void Setup() + { + var server = new TcpListener(IPAddress.Any, 1883); + + server.Start(1); + + var task = Task.Run(() => server.AcceptSocket()); + + var clientConnection = new TcpConnection(new IPEndPoint(IPAddress.Loopback, 1883)); + + _client = clientConnection.StartAsync().GetAwaiter().GetResult(); + + var serverConnection = new TcpConnection(task.GetAwaiter().GetResult()); + _server = serverConnection.StartAsync().GetAwaiter().GetResult(); + } + + + [Benchmark] + public async Task Send_10000_Chunks_Pipe() + { + var size = 5; + var iterations = 10000; + + await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size)); + } + + private async Task ReadAsync(int iterations, int size) + { + await Task.Yield(); + + var expected = iterations * size; + long read = 0; + var input = _client.Input; + + while (read < expected) + { + var readresult = await input.ReadAsync(CancellationToken.None).ConfigureAwait(false); + input.AdvanceTo(readresult.Buffer.End); + read += readresult.Buffer.Length; + } + } + + private async Task WriteAsync(int iterations, int size) + { + await Task.Yield(); + + var output = _server.Output; + + for (var i = 0; i < iterations; i++) + { + await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false); + } + } + } +} From 2578b380f952e2c42443015553fd9a5516878094 Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Fri, 1 Jun 2018 12:27:06 +0200 Subject: [PATCH 073/138] fixed sln change and moved options to seperate file --- MQTTnet.sln | 3 --- Tests/MQTTnet.Benchmarks/AllowNonOptimized.cs | 23 +++++++++++++++++++ .../ChannelAdapterBenchmark.cs | 21 ----------------- 3 files changed, 23 insertions(+), 24 deletions(-) create mode 100644 Tests/MQTTnet.Benchmarks/AllowNonOptimized.cs diff --git a/MQTTnet.sln b/MQTTnet.sln index a8370c66..6cb185db 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -41,9 +41,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Tests\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{998D04DD-7CB0-45F5-A393-E2495C16399E}" EndProject Global - GlobalSection(Performance) = preSolution - HasPerformanceSessions = true - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM diff --git a/Tests/MQTTnet.Benchmarks/AllowNonOptimized.cs b/Tests/MQTTnet.Benchmarks/AllowNonOptimized.cs new file mode 100644 index 00000000..ecb31a20 --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/AllowNonOptimized.cs @@ -0,0 +1,23 @@ +using System.Linq; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Validators; + +namespace MQTTnet.Benchmarks +{ + /// + /// this options may be used to run benchmarks in debugmode and attach a performance profiler + /// https://benchmarkdotnet.org/Configs/Configs.htm + /// + public class AllowNonOptimized : ManualConfig + { + public AllowNonOptimized() + { + Add(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS + Add(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default + Add(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default + Add(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default + Add(Job.InProcess); + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs index 5f66e6bc..726f5e0f 100644 --- a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -1,9 +1,4 @@ using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes.Exporters; -using BenchmarkDotNet.Attributes.Jobs; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Validators; using MQTTnet.Adapter; using MQTTnet.Core.Internal; using MQTTnet.Diagnostics; @@ -11,27 +6,11 @@ using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Serializer; using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; namespace MQTTnet.Benchmarks { - public class AllowNonOptimized : ManualConfig - { - public AllowNonOptimized() - { - Add(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS - Add(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default - Add(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default - Add(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default - Add(Job.InProcess); - } - } - [MemoryDiagnoser] public class ChannelAdapterBenchmark { From 66ebdfd61a226b62feaa499020383b8441639101 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 3 Jun 2018 22:04:43 +0200 Subject: [PATCH 074/138] Add test for simple server and client execution. --- Tests/MQTTnet.TestApp.NetCore/Program.cs | 5 ++++ .../ServerAndClientTest.cs | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 Tests/MQTTnet.TestApp.NetCore/ServerAndClientTest.cs diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 4032a173..270b16ed 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -20,6 +20,7 @@ namespace MQTTnet.TestApp.NetCore Console.WriteLine("3 = Start performance test"); Console.WriteLine("4 = Start managed client"); Console.WriteLine("5 = Start public broker test"); + Console.WriteLine("6 = Start server & client"); var pressedKey = Console.ReadKey(true); if (pressedKey.KeyChar == '1') @@ -42,6 +43,10 @@ namespace MQTTnet.TestApp.NetCore { Task.Run(PublicBrokerTest.RunAsync); } + else if (pressedKey.KeyChar == '6') + { + Task.Run(ServerAndClientTest.RunAsync); + } Thread.Sleep(Timeout.Infinite); } diff --git a/Tests/MQTTnet.TestApp.NetCore/ServerAndClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ServerAndClientTest.cs new file mode 100644 index 00000000..30ba9140 --- /dev/null +++ b/Tests/MQTTnet.TestApp.NetCore/ServerAndClientTest.cs @@ -0,0 +1,27 @@ +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Client; +using MQTTnet.Server; + +namespace MQTTnet.TestApp.NetCore +{ + public static class ServerAndClientTest + { + public static async Task RunAsync() + { + MqttNetConsoleLogger.ForwardToConsole(); + + var factory = new MqttFactory(); + var server = factory.CreateMqttServer(); + var client = factory.CreateMqttClient(); + + var serverOptions = new MqttServerOptionsBuilder().Build(); + await server.StartAsync(serverOptions); + + var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(); + await client.ConnectAsync(clientOptions); + + await Task.Delay(Timeout.Infinite); + } + } +} From b38df9eecd1bc6416816664e6388baa406112dcf Mon Sep 17 00:00:00 2001 From: Christoph Stichlberger Date: Mon, 4 Jun 2018 16:00:03 +0200 Subject: [PATCH 075/138] Respect keep alive interval after sending --- Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index 90790d5a..ae8442ee 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -419,12 +419,14 @@ namespace MQTTnet.Client keepAliveSendInterval = _options.KeepAliveSendInterval.Value; } - if (_sendTracker.Elapsed > keepAliveSendInterval) + var waitTime = keepAliveSendInterval - _sendTracker.Elapsed; + if (waitTime <= TimeSpan.Zero) { await SendAndReceiveAsync(new MqttPingReqPacket()).ConfigureAwait(false); + waitTime = keepAliveSendInterval; } - await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).ConfigureAwait(false); + await Task.Delay(waitTime, _cancellationTokenSource.Token).ConfigureAwait(false); } } catch (Exception exception) From e6de4d014e0049d61a9d3e07f1450bac168bf67b Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Mon, 4 Jun 2018 21:19:15 +0200 Subject: [PATCH 076/138] Update client test app --- Tests/MQTTnet.TestApp.NetCore/ClientTest.cs | 29 ++++++++------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs index dd4a1bc0..b943bc3f 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs @@ -12,25 +12,18 @@ namespace MQTTnet.TestApp.NetCore { try { - var options = new MqttClientOptions - { - ClientId = "XYZ", - CleanSession = true, - ChannelOptions = new MqttClientTcpOptions - { - //Server = "localhost", - Server = "192.168.1.174" - }, - //ChannelOptions = new MqttClientWebSocketOptions - //{ - // Uri = "ws://localhost:59690/mqtt" - //} - }; + MqttNetConsoleLogger.ForwardToConsole(); var factory = new MqttFactory(); - var client = factory.CreateMqttClient(); - + var clientOptions = new MqttClientOptions + { + ChannelOptions = new MqttClientTcpOptions + { + Server = "localhost" + } + }; + client.ApplicationMessageReceived += (s, e) => { Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); @@ -57,7 +50,7 @@ namespace MQTTnet.TestApp.NetCore try { - await client.ConnectAsync(options); + await client.ConnectAsync(clientOptions); } catch { @@ -67,7 +60,7 @@ namespace MQTTnet.TestApp.NetCore try { - await client.ConnectAsync(options); + await client.ConnectAsync(clientOptions); } catch (Exception exception) { From afe25db0d33c297fcb136a122d4fa7d74bc95829 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Mon, 4 Jun 2018 21:26:33 +0200 Subject: [PATCH 077/138] Update docs. --- Build/MQTTnet.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 7dc215f1..0f0049c1 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -16,6 +16,7 @@ * [Core] Log messages now contain the complete source path including parent components. * [Core] The adapter now has an _Endpoint_ definition as string containing remote IP and port. * [Client] Received messages are now processed in the worker thread by default. Added a new setting for switching back to dedicated threads. +* [Client] Fixed wrong calculation for sending keep alive packets (thanks to @cstichlberger) * [ManagedClient] Fixed a loading issue of stored application messages (thanks to @JTrotta). * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot). * [Server] The takeover of an existing client sessions is now treated as a _clean_ disconnect of the previous client. From 39edd22a9d482b17d5ab1c7b55e4c93b223ee326 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Mon, 4 Jun 2018 22:19:07 +0200 Subject: [PATCH 078/138] Refactor thread management for incoming packets. --- Build/MQTTnet.nuspec | 2 +- .../Adapter/MqttChannelAdapter.cs | 3 +- .../Client/IMqttClientOptions.cs | 3 +- .../MQTTnet.NetStandard/Client/MqttClient.cs | 77 +++++-------------- .../Client/MqttClientOptions.cs | 3 +- .../Client/MqttClientOptionsBuilder.cs | 7 -- .../Client/MqttPacketDispatcher.cs | 9 ++- ...eceivedApplicationMessageProcessingMode.cs | 8 -- 8 files changed, 31 insertions(+), 81 deletions(-) delete mode 100644 Frameworks/MQTTnet.NetStandard/Client/MqttReceivedApplicationMessageProcessingMode.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 0f0049c1..fc9e1a6f 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -15,7 +15,7 @@ * [Core] Added several packet validations. * [Core] Log messages now contain the complete source path including parent components. * [Core] The adapter now has an _Endpoint_ definition as string containing remote IP and port. -* [Client] Received messages are now processed in the worker thread by default. Added a new setting for switching back to dedicated threads. +* [Client] Received messages are now processed completely in the worker thread without creating new Tasks. * [Client] Fixed wrong calculation for sending keep alive packets (thanks to @cstichlberger) * [ManagedClient] Fixed a loading issue of stored application messages (thanks to @JTrotta). * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot). diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 92c8f881..941feeb0 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -135,9 +135,10 @@ namespace MQTTnet.Adapter { var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, cancellationToken).ConfigureAwait(false); - ReadingPacketStarted?.Invoke(this, EventArgs.Empty); try { + ReadingPacketStarted?.Invoke(this, EventArgs.Empty); + var bodyLength = await MqttPacketReader.ReadBodyLengthAsync(channel, cancellationToken).ConfigureAwait(false); if (bodyLength == 0) { diff --git a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientOptions.cs index aa873af0..176f5e43 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientOptions.cs @@ -14,8 +14,7 @@ namespace MQTTnet.Client TimeSpan CommunicationTimeout { get; } TimeSpan KeepAlivePeriod { get; } TimeSpan? KeepAliveSendInterval { get; } - MqttReceivedApplicationMessageProcessingMode ReceivedApplicationMessageProcessingMode { get; } - + MqttApplicationMessage WillMessage { get; } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index f7267d2a..eeb3d333 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -19,7 +19,7 @@ namespace MQTTnet.Client private readonly Stopwatch _sendTracker = new Stopwatch(); private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1); private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); - + private readonly IMqttClientAdapterFactory _adapterFactory; private readonly IMqttNetChildLogger _logger; @@ -390,25 +390,7 @@ namespace MQTTnet.Client while (!cancellationToken.IsCancellationRequested) { var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); - - if (cancellationToken.IsCancellationRequested) - { - return; - } - - if (packet == null) - { - continue; - } - - if (_options.ReceivedApplicationMessageProcessingMode == MqttReceivedApplicationMessageProcessingMode.SingleThread) - { - await ProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false); - } - else if (_options.ReceivedApplicationMessageProcessingMode == MqttReceivedApplicationMessageProcessingMode.DedicatedThread) - { - StartProcessReceivedPacketAsync(packet, cancellationToken); - } + await ProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false); } } catch (Exception exception) @@ -434,40 +416,30 @@ namespace MQTTnet.Client } } - private async Task ProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) + private Task ProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) { - try + if (packet is MqttPublishPacket publishPacket) { - if (packet is MqttPublishPacket publishPacket) - { - await ProcessReceivedPublishPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false); - return; - } - - if (packet is MqttPingReqPacket) - { - await SendAsync(new MqttPingRespPacket(), cancellationToken).ConfigureAwait(false); - return; - } - - if (packet is MqttDisconnectPacket) - { - await DisconnectAsync().ConfigureAwait(false); - return; - } - - if (packet is MqttPubRelPacket pubRelPacket) - { - await ProcessReceivedPubRelPacket(pubRelPacket, cancellationToken).ConfigureAwait(false); - return; - } - - _packetDispatcher.Dispatch(packet); + return ProcessReceivedPublishPacketAsync(publishPacket, cancellationToken); } - catch (Exception exception) + + if (packet is MqttPingReqPacket) { - _logger.Error(exception, "Unhandled exception while processing received packet."); + return SendAsync(new MqttPingRespPacket(), cancellationToken); } + + if (packet is MqttDisconnectPacket) + { + return DisconnectAsync(); + } + + if (packet is MqttPubRelPacket pubRelPacket) + { + return ProcessReceivedPubRelPacket(pubRelPacket, cancellationToken); + } + + _packetDispatcher.Dispatch(packet); + return Task.FromResult(0); } private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) @@ -518,13 +490,6 @@ namespace MQTTnet.Client cancellationToken); } - private void StartProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) - { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(() => ProcessReceivedPacketAsync(packet, cancellationToken), cancellationToken); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - } - private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket) { try diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptions.cs index ae0517a6..7d3ad5d8 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptions.cs @@ -14,8 +14,7 @@ namespace MQTTnet.Client public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10); public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15); public TimeSpan? KeepAliveSendInterval { get; set; } - public MqttReceivedApplicationMessageProcessingMode ReceivedApplicationMessageProcessingMode { get; set; } = MqttReceivedApplicationMessageProcessingMode.SingleThread; - + public MqttApplicationMessage WillMessage { get; set; } } } diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs index 2eefb813..985cda7c 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs @@ -114,13 +114,6 @@ namespace MQTTnet.Client return this; } - public MqttClientOptionsBuilder WithReceivedApplicationMessageProcessingMode( - MqttReceivedApplicationMessageProcessingMode mode) - { - _options.ReceivedApplicationMessageProcessingMode = mode; - return this; - } - public IMqttClientOptions Build() { if (_tlsOptions != null) diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs index 1a1021c4..417b3510 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs @@ -13,7 +13,7 @@ namespace MQTTnet.Client { foreach (var awaiter in _awaiters) { - awaiter.Value.SetException(exception); + Task.Run(() => awaiter.Value.SetException(exception)); // Task.Run fixes a dead lock. Without this the client only receives one message. } } @@ -30,9 +30,10 @@ namespace MQTTnet.Client var type = packet.GetType(); var key = new Tuple(identifier, type); - if (_awaiters.TryRemove(key, out var tcs)) + if (_awaiters.TryRemove(key, out var awaiter)) { - tcs.TrySetResult(packet); + awaiter.SetResult(packet); + Task.Run(() => awaiter.SetResult(packet)); // Task.Run fixes a dead lock. Without this the client only receives one message. return; } @@ -70,7 +71,7 @@ namespace MQTTnet.Client } var key = new Tuple(identifier ?? 0, typeof(TResponsePacket)); - _awaiters.TryRemove(key, out var _); + _awaiters.TryRemove(key, out _); } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttReceivedApplicationMessageProcessingMode.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttReceivedApplicationMessageProcessingMode.cs deleted file mode 100644 index 651abe5b..00000000 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttReceivedApplicationMessageProcessingMode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MQTTnet.Client -{ - public enum MqttReceivedApplicationMessageProcessingMode - { - SingleThread, - DedicatedThread - } -} From c88b46783b069d1670caba12b67aa129f75e7b0c Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Mon, 4 Jun 2018 22:33:13 +0200 Subject: [PATCH 079/138] [Server] Add new interceptor before messages are added to a queue. --- Build/MQTTnet.nuspec | 1 + .../Server/IMqttServerOptions.cs | 1 + ...qttClientMessageQueueInterceptorContext.cs | 20 +++++++++++++++ .../Server/MqttClientPendingMessagesQueue.cs | 3 +-- .../Server/MqttClientSession.cs | 25 +++++++++++++++---- .../Server/MqttClientSessionsManager.cs | 2 +- .../Server/MqttServerOptions.cs | 2 ++ 7 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttClientMessageQueueInterceptorContext.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index fc9e1a6f..e55f08f4 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -24,6 +24,7 @@ * [Server] Keep alive checking is now suspended while large packages are being received (and thus the client is connected). Keep alive checking continues after a large packet is received completely. * [Server] Rewritten the _ConnectedClients_ API and added new features for disconnecting and Endpoint information (IP etc.). * [Server] Added settings for disabling persistent sessions and defining a max pending messages queue size per session. +* [Server] Added a new interceptor which is invoked before a new message is added to the client queue. Copyright Christian Kratky 2016-2018 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 diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs index 54e037d8..b753587d 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs @@ -16,6 +16,7 @@ namespace MQTTnet.Server Action ConnectionValidator { get; } Action SubscriptionInterceptor { get; } Action ApplicationMessageInterceptor { get; } + Action ClientMessageQueueInterceptor { get; set; } MqttServerDefaultEndpointOptions DefaultEndpointOptions { get; } MqttServerTlsEndpointOptions TlsEndpointOptions { get; } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientMessageQueueInterceptorContext.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientMessageQueueInterceptorContext.cs new file mode 100644 index 00000000..ca0e2a0e --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientMessageQueueInterceptorContext.cs @@ -0,0 +1,20 @@ +namespace MQTTnet.Server +{ + public class MqttClientMessageQueueInterceptorContext + { + public MqttClientMessageQueueInterceptorContext(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage) + { + SenderClientId = senderClientId; + ReceiverClientId = receiverClientId; + ApplicationMessage = applicationMessage; + } + + public string SenderClientId { get; } + + public string ReceiverClientId { get; } + + public MqttApplicationMessage ApplicationMessage { get; set; } + + public bool AcceptEnqueue { get; set; } = true; + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs index 97f616c0..15d4af90 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs @@ -19,7 +19,6 @@ namespace MQTTnet.Server private readonly IMqttNetChildLogger _logger; private ConcurrentQueue _queue = new ConcurrentQueue(); - private Task _workerTask; public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger) { @@ -41,7 +40,7 @@ namespace MQTTnet.Server return; } - _workerTask = Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken); + Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken); } public void Enqueue(MqttBasePacket packet) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs index 0f115534..4fd72667 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs @@ -153,24 +153,39 @@ namespace MQTTnet.Server } } - public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage) + public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - var result = _subscriptionsManager.CheckSubscriptions(applicationMessage); - if (!result.IsSubscribed) + var checkSubscriptionsResult = _subscriptionsManager.CheckSubscriptions(applicationMessage); + if (!checkSubscriptionsResult.IsSubscribed) { return; } var publishPacket = applicationMessage.ToPublishPacket(); - publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel; + publishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel; if (publishPacket.QualityOfServiceLevel > 0) { publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); } + if (_options.ClientMessageQueueInterceptor != null) + { + var context = new MqttClientMessageQueueInterceptorContext( + senderClientSession?.ClientId, + ClientId, + publishPacket.ToApplicationMessage()); + + _options.ClientMessageQueueInterceptor?.Invoke(context); + + if (!context.AcceptEnqueue || context.ApplicationMessage == null) + { + return; + } + } + _pendingMessagesQueue.Enqueue(publishPacket); } @@ -276,7 +291,7 @@ namespace MQTTnet.Server var retainedMessages = _retainedMessagesManager.GetSubscribedMessages(topicFilters); foreach (var applicationMessage in retainedMessages) { - EnqueueApplicationMessage(applicationMessage); + EnqueueApplicationMessage(null, applicationMessage); } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index 7918aa8d..692e9c6c 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -257,7 +257,7 @@ namespace MQTTnet.Server foreach (var clientSession in _sessions.Values) { - clientSession.EnqueueApplicationMessage(applicationMessage); + clientSession.EnqueueApplicationMessage(senderClientSession, applicationMessage); } } catch (Exception exception) diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs index e8485554..4d06b942 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs @@ -21,6 +21,8 @@ namespace MQTTnet.Server public Action ApplicationMessageInterceptor { get; set; } + public Action ClientMessageQueueInterceptor { get; set; } + public Action SubscriptionInterceptor { get; set; } public IMqttServerStorage Storage { get; set; } From 31b4eb7937722a3e5bfdf92136b7da83c987a89a Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 5 Jun 2018 21:13:58 +0200 Subject: [PATCH 080/138] Optimize package serializing and dispatching. --- .../Adapter/MqttChannelAdapter.cs | 21 +++--- .../Client/MqttPacketDispatcher.cs | 5 +- .../Serializer/MqttFixedHeader.cs | 15 +++++ .../Serializer/MqttPacketReader.cs | 66 +++++++++++-------- .../Serializer/MqttPacketSerializer.cs | 28 +++++--- .../Serializer/MqttPacketWriter.cs | 17 +++-- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 9 ++- .../MqttPacketReaderTests.cs | 6 +- .../MqttPacketSerializerTests.cs | 7 +- 9 files changed, 103 insertions(+), 71 deletions(-) create mode 100644 Frameworks/MQTTnet.NetStandard/Serializer/MqttFixedHeader.cs diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 941feeb0..73588efa 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -139,37 +139,36 @@ namespace MQTTnet.Adapter { ReadingPacketStarted?.Invoke(this, EventArgs.Empty); - var bodyLength = await MqttPacketReader.ReadBodyLengthAsync(channel, cancellationToken).ConfigureAwait(false); - if (bodyLength == 0) + if (fixedHeader.RemainingLength == 0) { - return new ReceivedMqttPacket(fixedHeader, null); + return new ReceivedMqttPacket(fixedHeader.Flags, null); } - var body = new MemoryStream(bodyLength); + var body = new MemoryStream(fixedHeader.RemainingLength); - var buffer = new byte[Math.Min(ReadBufferSize, bodyLength)]; - while (body.Length < bodyLength) + var buffer = new byte[Math.Min(ReadBufferSize, fixedHeader.RemainingLength)]; + while (body.Length < fixedHeader.RemainingLength) { - var bytesLeft = bodyLength - (int)body.Length; + var bytesLeft = fixedHeader.RemainingLength - (int)body.Length; if (bytesLeft > buffer.Length) { bytesLeft = buffer.Length; } - var readBytesCount = await channel.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false); - if (readBytesCount <= 0) + var readBytes = await channel.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false); + if (readBytes <= 0) { ExceptionHelper.ThrowGracefulSocketClose(); } // Here is no need to await because internally only an array is used and no real I/O operation is made. // Using async here will only generate overhead. - body.Write(buffer, 0, readBytesCount); + body.Write(buffer, 0, readBytes); } body.Seek(0L, SeekOrigin.Begin); - return new ReceivedMqttPacket(fixedHeader, body); + return new ReceivedMqttPacket(fixedHeader.Flags, body); } finally { diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs index 417b3510..dec106db 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs @@ -13,7 +13,7 @@ namespace MQTTnet.Client { foreach (var awaiter in _awaiters) { - Task.Run(() => awaiter.Value.SetException(exception)); // Task.Run fixes a dead lock. Without this the client only receives one message. + Task.Run(() => awaiter.Value.TrySetException(exception)); // Task.Run fixes a dead lock. Without this the client only receives one message. } } @@ -32,8 +32,7 @@ namespace MQTTnet.Client if (_awaiters.TryRemove(key, out var awaiter)) { - awaiter.SetResult(packet); - Task.Run(() => awaiter.SetResult(packet)); // Task.Run fixes a dead lock. Without this the client only receives one message. + Task.Run(() => awaiter.TrySetResult(packet)); // Task.Run fixes a dead lock. Without this the client only receives one message. return; } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttFixedHeader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttFixedHeader.cs new file mode 100644 index 00000000..a8c2015b --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttFixedHeader.cs @@ -0,0 +1,15 @@ +namespace MQTTnet.Serializer +{ + public struct MqttFixedHeader + { + public MqttFixedHeader(byte flags, int remainingLength) + { + Flags = flags; + RemainingLength = remainingLength; + } + + public byte Flags { get; } + + public int RemainingLength { get; } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs index 4b3b6b17..a7b0f9d6 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs @@ -11,19 +11,32 @@ namespace MQTTnet.Serializer { public static class MqttPacketReader { - public static async Task ReadFixedHeaderAsync(IMqttChannel channel, CancellationToken cancellationToken) + public static async Task ReadFixedHeaderAsync(IMqttChannel channel, CancellationToken cancellationToken) { - // Wait for the next package which starts with the header. At this point there will probably - // some large delay and thus the thread should be put back to the pool (await). So ReadByte() - // is not an option here. - var buffer = new byte[1]; - var readCount = await channel.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); - if (readCount <= 0) + // 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]; + var totalBytesRead = 0; + + while (totalBytesRead < buffer.Length) { - ExceptionHelper.ThrowGracefulSocketClose(); + var bytesRead = await channel.ReadAsync(buffer, 0, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false); + if (bytesRead <= 0) + { + ExceptionHelper.ThrowGracefulSocketClose(); + } + + totalBytesRead += bytesRead; } - return buffer[0]; + var hasRemainingLength = buffer[1] != 0; + if (!hasRemainingLength) + { + return new MqttFixedHeader(buffer[0], 0); + } + + var bodyLength = await ReadBodyLengthAsync(channel, buffer[1], cancellationToken); + return new MqttFixedHeader(buffer[0], bodyLength); } public static ushort ReadUInt16(this Stream stream) @@ -64,21 +77,29 @@ namespace MQTTnet.Serializer return stream.ReadBytes((int)(stream.Length - stream.Position)); } - public static async Task ReadBodyLengthAsync(IMqttChannel channel, CancellationToken cancellationToken) + private static byte[] ReadBytes(this Stream stream, int count) + { + var buffer = new byte[count]; + var readBytes = stream.Read(buffer, 0, count); + + if (readBytes != count) + { + throw new InvalidOperationException($"Unable to read {count} bytes from the stream."); + } + + return buffer; + } + + private static async Task ReadBodyLengthAsync(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 = 1; - var value = 0; - int encodedByte; + var value = (byte)(initialEncodedByte & 127) * multiplier; + int encodedByte = initialEncodedByte; var buffer = new byte[1]; - do + while ((encodedByte & 128) != 0) { - if (cancellationToken.IsCancellationRequested) - { - throw new TaskCanceledException(); - } - var readCount = await channel.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); if (readCount <= 0) { @@ -94,16 +115,9 @@ namespace MQTTnet.Serializer } multiplier *= 128; - } while ((encodedByte & 128) != 0); + } return value; } - - private static byte[] ReadBytes(this Stream stream, int count) - { - var buffer = new byte[count]; - stream.Read(buffer, 0, count); - return buffer; - } } } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 8c384e07..f397c0c8 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -10,6 +10,8 @@ namespace MQTTnet.Serializer { public class MqttPacketSerializer : IMqttPacketSerializer { + private const int FixedHeaderSize = 1; + public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; public ArraySegment Serialize(MqttBasePacket packet) @@ -18,27 +20,33 @@ namespace MQTTnet.Serializer using (var stream = new MemoryStream(128)) { - // Leave enough head space for max header size (fixed + 4 variable remaining length) - stream.Position = 5; + // Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes) + stream.Seek(5, SeekOrigin.Begin); + var fixedHeader = SerializePacket(packet, stream); + var remainingLength = (int)stream.Length - 5; - stream.Position = 1; - var remainingLength = MqttPacketWriter.EncodeRemainingLength((int)stream.Length - 5, stream); + var remainingLengthSize = MqttPacketWriter.WriteRemainingLength(remainingLength, stream); - var headerSize = remainingLength + 1; + var headerSize = FixedHeaderSize + remainingLengthSize; var headerOffset = 5 - headerSize; // Position cursor on correct offset on beginining of array (has leading 0x0) - stream.Position = headerOffset; - + stream.Seek(headerOffset, SeekOrigin.Begin); stream.WriteByte(fixedHeader); #if NET461 || NET452 || NETSTANDARD2_0 var buffer = stream.GetBuffer(); -#else - var buffer = stream.ToArray(); -#endif return new ArraySegment(buffer, headerOffset, (int)stream.Length - headerOffset); +#else + if (stream.TryGetBuffer(out var segment)) + { + return new ArraySegment(segment.Array, headerOffset, segment.Count - headerOffset); + } + + var buffer = stream.ToArray(); + return new ArraySegment(buffer, headerOffset, buffer.Length - headerOffset); +#endif } } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs index 1d040183..3206145d 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs @@ -41,19 +41,18 @@ namespace MQTTnet.Serializer stream.Write(value, 0, length); } - public static int EncodeRemainingLength(int length, MemoryStream stream) + public static int WriteRemainingLength(int length, MemoryStream stream) { // write the encoded remaining length right aligned on the 4 byte buffer - if (length <= 0) { - stream.Seek(3, SeekOrigin.Current); + stream.Seek(4, SeekOrigin.Begin); stream.WriteByte(0); return 1; } var buffer = new byte[4]; - var offset = 0; + var remainingLengthSize = 0; // Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html. var x = length; @@ -66,15 +65,15 @@ namespace MQTTnet.Serializer encodedByte = encodedByte | 128; } - buffer[offset] = (byte)encodedByte; + buffer[remainingLengthSize] = (byte)encodedByte; - offset++; + remainingLengthSize++; } while (x > 0); - stream.Seek(4 - offset, SeekOrigin.Current); - stream.Write(buffer, 0, offset); + stream.Seek(5 - remainingLengthSize, SeekOrigin.Begin); + stream.Write(buffer, 0, remainingLengthSize); - return offset; + return remainingLengthSize; } } } diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index 1b2e2384..389998f6 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -51,12 +51,11 @@ namespace MQTTnet.Benchmarks { var channel = new TestMqttChannel(headerStream); - var header = MqttPacketReader.ReadFixedHeaderAsync(new TestMqttChannel(headerStream), CancellationToken.None).GetAwaiter().GetResult(); - var bodyLength = MqttPacketReader.ReadBodyLengthAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); - - using (var bodyStream = new MemoryStream(Join(_serializedPacket), (int)headerStream.Position, bodyLength)) + 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((byte)header, bodyStream)); + _serializer.Deserialize(new ReceivedMqttPacket(header.Flags, bodyStream)); } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs index 076dc5f9..58ef6b11 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs @@ -2,6 +2,7 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Core.Internal; +using MQTTnet.Exceptions; using MQTTnet.Serializer; namespace MQTTnet.Core.Tests @@ -10,11 +11,10 @@ namespace MQTTnet.Core.Tests public class MqttPacketReaderTests { [TestMethod] + [ExpectedException(typeof(MqttCommunicationException))] public void MqttPacketReader_EmptyStream() { - var header = MqttPacketReader.ReadFixedHeaderAsync(new TestMqttChannel(new MemoryStream()), CancellationToken.None).GetAwaiter().GetResult(); - - Assert.AreEqual(-1, header); + MqttPacketReader.ReadFixedHeaderAsync(new TestMqttChannel(new MemoryStream()), CancellationToken.None).GetAwaiter().GetResult(); } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 606ea099..6ba3b124 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -419,11 +419,10 @@ namespace MQTTnet.Core.Tests { var channel = new TestMqttChannel(headerStream); var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); - var bodyLength = MqttPacketReader.ReadBodyLengthAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); - - using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, bodyLength)) + + using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) { - var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket((byte)header, bodyStream)); + var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header.Flags, bodyStream)); var buffer2 = serializer.Serialize(deserializedPacket); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2))); From 1d455221e475a781429e51e8850eeab7f9d81c04 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 5 Jun 2018 21:28:11 +0200 Subject: [PATCH 081/138] Clear awaiters after an exception has occured. --- Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs index dec106db..fd205c51 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs @@ -15,6 +15,8 @@ namespace MQTTnet.Client { Task.Run(() => awaiter.Value.TrySetException(exception)); // Task.Run fixes a dead lock. Without this the client only receives one message. } + + _awaiters.Clear(); } public void Dispatch(MqttBasePacket packet) From 00ac9ce961ba974b2fd1b505e44c7d56c2ce824c Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 5 Jun 2018 23:39:59 +0200 Subject: [PATCH 082/138] Fix wrong remaining length calculation. --- .../Serializer/MqttPacketReader.cs | 6 +++--- .../Serializer/MqttPacketSerializer.cs | 5 +++-- .../Serializer/MqttPacketWriter.cs | 20 +++++++------------ 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs index a7b0f9d6..110407e2 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs @@ -93,11 +93,11 @@ namespace MQTTnet.Serializer private static async Task ReadBodyLengthAsync(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 = 1; - var value = (byte)(initialEncodedByte & 127) * multiplier; + var multiplier = 128; + var value = initialEncodedByte & 127; int encodedByte = initialEncodedByte; var buffer = new byte[1]; - + while ((encodedByte & 128) != 0) { var readCount = await channel.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index f397c0c8..32001db3 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -26,14 +26,15 @@ namespace MQTTnet.Serializer var fixedHeader = SerializePacket(packet, stream); var remainingLength = (int)stream.Length - 5; - var remainingLengthSize = MqttPacketWriter.WriteRemainingLength(remainingLength, stream); + var remainingLengthBuffer = MqttPacketWriter.EncodeRemainingLength(remainingLength); - var headerSize = FixedHeaderSize + remainingLengthSize; + 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); #if NET461 || NET452 || NETSTANDARD2_0 var buffer = stream.GetBuffer(); diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs index 3206145d..2cc9d7bb 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs @@ -41,20 +41,18 @@ namespace MQTTnet.Serializer stream.Write(value, 0, length); } - public static int WriteRemainingLength(int length, MemoryStream stream) + public static ArraySegment EncodeRemainingLength(int length) { // write the encoded remaining length right aligned on the 4 byte buffer if (length <= 0) { - stream.Seek(4, SeekOrigin.Begin); - stream.WriteByte(0); - return 1; + return new ArraySegment(new byte[1], 0, 1); } var buffer = new byte[4]; - var remainingLengthSize = 0; + var bufferOffset = 0; - // Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html. + // Algorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html. var x = length; do { @@ -65,15 +63,11 @@ namespace MQTTnet.Serializer encodedByte = encodedByte | 128; } - buffer[remainingLengthSize] = (byte)encodedByte; - - remainingLengthSize++; + buffer[bufferOffset] = (byte)encodedByte; + bufferOffset++; } while (x > 0); - stream.Seek(5 - remainingLengthSize, SeekOrigin.Begin); - stream.Write(buffer, 0, remainingLengthSize); - - return remainingLengthSize; + return new ArraySegment(buffer, 0, bufferOffset); } } } From 70c5bd187546b89f434ba8bb46884e0cde2268de Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 5 Jun 2018 23:41:02 +0200 Subject: [PATCH 083/138] Remove obsolete files. --- .../Server/MqttServerEventDispatcher.cs | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttServerEventDispatcher.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerEventDispatcher.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerEventDispatcher.cs deleted file mode 100644 index 53d0e82d..00000000 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerEventDispatcher.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace MQTTnet.Server -{ - public class MqttServerEventDispatcher - { - public event EventHandler ClientConnected; - public event EventHandler ClientDisconnected; - public event EventHandler ClientSubscribedTopic; - public event EventHandler ClientUnsubscribedTopic; - - public event EventHandler ApplicationMessageReceived; - } -} From fd1880f1ff46090310625a0c6df3d2d4bd0718d5 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Jun 2018 21:26:56 +0200 Subject: [PATCH 084/138] Update better code hub config. --- .bettercodehub.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.bettercodehub.yml b/.bettercodehub.yml index a24cd289..73ad89d2 100644 --- a/.bettercodehub.yml +++ b/.bettercodehub.yml @@ -1,8 +1,5 @@ exclude: -- /Frameworks/.* -- /MQTTnet.TestApp.NetCore/Program.cs -- /Tests/MQTTnet.TestApp.NetFramework/.* -- /Tests/MQTTnet.TestApp.UniversalWindows/.* -component_depth: 1 +- /Tests/.* +component_depth: 2 languages: - csharp From b05cb0d2ea6eac6a1e1c76d11b3de1e490479dc2 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Jun 2018 21:27:39 +0200 Subject: [PATCH 085/138] Move managed client to separate nuget. --- Build/MQTTnet.AspNetCore.nuspec | 6 +-- Build/MQTTnet.Extensions.ManagedClient.nuspec | 53 +++++++++++++++++++ Build/MQTTnet.Extensions.Rpc.nuspec | 22 ++++---- Build/MQTTnet.nuspec | 5 +- Build/build.ps1 | 10 +++- .../ApplicationMessageProcessedEventArgs.cs | 2 +- .../IManagedMqttClient.cs | 2 +- .../IManagedMqttClientOptions.cs | 2 +- .../IManagedMqttClientStorage.cs | 2 +- .../MQTTnet.Extensions.ManagedClient.csproj | 31 +++++++++++ .../ManagedMqttApplicationMessage.cs | 11 ++++ .../ManagedMqttClient.cs | 2 +- .../ManagedMqttClientExtensions.cs | 2 +- .../ManagedMqttClientOptions.cs | 2 +- .../ManagedMqttClientOptionsBuilder.cs | 2 +- .../ManagedMqttClientStorageManager.cs | 2 +- .../MqttFactoryExtensions.cs | 24 +++++++++ .../ReconnectionResult.cs | 2 +- .../Client/IMqttClientFactory.cs | 5 -- 19 files changed, 156 insertions(+), 31 deletions(-) create mode 100644 Build/MQTTnet.Extensions.ManagedClient.nuspec rename {Frameworks/MQTTnet.NetStandard/ManagedClient => Extensions/MQTTnet.Extensions.ManagedClient}/ApplicationMessageProcessedEventArgs.cs (93%) rename {Frameworks/MQTTnet.NetStandard/ManagedClient => Extensions/MQTTnet.Extensions.ManagedClient}/IManagedMqttClient.cs (94%) rename {Frameworks/MQTTnet.NetStandard/ManagedClient => Extensions/MQTTnet.Extensions.ManagedClient}/IManagedMqttClientOptions.cs (84%) rename {Frameworks/MQTTnet.NetStandard/ManagedClient => Extensions/MQTTnet.Extensions.ManagedClient}/IManagedMqttClientStorage.cs (86%) create mode 100644 Extensions/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj create mode 100644 Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessage.cs rename {Frameworks/MQTTnet.NetStandard/ManagedClient => Extensions/MQTTnet.Extensions.ManagedClient}/ManagedMqttClient.cs (99%) rename {Frameworks/MQTTnet.NetStandard/ManagedClient => Extensions/MQTTnet.Extensions.ManagedClient}/ManagedMqttClientExtensions.cs (97%) rename {Frameworks/MQTTnet.NetStandard/ManagedClient => Extensions/MQTTnet.Extensions.ManagedClient}/ManagedMqttClientOptions.cs (88%) rename {Frameworks/MQTTnet.NetStandard/ManagedClient => Extensions/MQTTnet.Extensions.ManagedClient}/ManagedMqttClientOptionsBuilder.cs (98%) rename {Frameworks/MQTTnet.NetStandard/ManagedClient => Extensions/MQTTnet.Extensions.ManagedClient}/ManagedMqttClientStorageManager.cs (97%) create mode 100644 Extensions/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs rename {Frameworks/MQTTnet.NetStandard/ManagedClient => Extensions/MQTTnet.Extensions.ManagedClient}/ReconnectionResult.cs (71%) diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec index 4245077b..30abe1cd 100644 --- a/Build/MQTTnet.AspNetCore.nuspec +++ b/Build/MQTTnet.AspNetCore.nuspec @@ -2,7 +2,7 @@ MQTTnet.AspNetCore - 2.7.5 + 0.0.0 Christian Kratky Christian Kratky https://github.com/chkr1011/MQTTnet/blob/master/LICENSE @@ -10,13 +10,13 @@ https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png false This is a support library to integrate MQTTnet into AspNetCore. - * Updated to MQTTnet 2.7.5. + * Updated to MQTTnet 2.8.0. Copyright Christian Kratky 2016-2018 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 - + diff --git a/Build/MQTTnet.Extensions.ManagedClient.nuspec b/Build/MQTTnet.Extensions.ManagedClient.nuspec new file mode 100644 index 00000000..10835bf7 --- /dev/null +++ b/Build/MQTTnet.Extensions.ManagedClient.nuspec @@ -0,0 +1,53 @@ + + + + MQTTnet.Extensions.ManagedClient + 0.0.0 + Christian Kratky + Christian Kratky + https://github.com/chkr1011/MQTTnet/blob/master/LICENSE + https://github.com/chkr1011/MQTTnet + https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png + false + This is an extension library which provides a managed MQTT client with additional features using MQTTnet. + * Updated to MQTTnet 2.8.0. + + Copyright Christian Kratky 2016-2018 + 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec index 8bca0b57..f1e2d3a0 100644 --- a/Build/MQTTnet.Extensions.Rpc.nuspec +++ b/Build/MQTTnet.Extensions.Rpc.nuspec @@ -2,46 +2,46 @@ MQTTnet.Extensions.Rpc - 2.7.5 + 0.0.0 Christian Kratky Christian Kratky https://github.com/chkr1011/MQTTnet/blob/master/LICENSE https://github.com/chkr1011/MQTTnet https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png false - This is a extension library which allows executing synchronous device calls including a response using MQTTnet. - * Updated to MQTTnet 2.7.5. + This is an extension library which allows executing synchronous device calls including a response using MQTTnet. + * Updated to MQTTnet 2.8.0. Copyright Christian Kratky 2016-2018 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 - + - + - + - + - + - + - + - + diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index e55f08f4..8e09d921 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -2,7 +2,7 @@ MQTTnet - 2.8.0 + 0.0.0 Christian Kratky Christian Kratky https://github.com/chkr1011/MQTTnet/blob/master/LICENSE @@ -17,6 +17,9 @@ * [Core] The adapter now has an _Endpoint_ definition as string containing remote IP and port. * [Client] Received messages are now processed completely in the worker thread without creating new Tasks. * [Client] Fixed wrong calculation for sending keep alive packets (thanks to @cstichlberger) +* [Client] A clean disconnect (via DisconnectAsync) will no longer throw an exception. +* [ManagedClient] The managed client is moved to a separate nuget package. +* [ManagedClient] Added an own message format with extended properties like ID. * [ManagedClient] Fixed a loading issue of stored application messages (thanks to @JTrotta). * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot). * [Server] The takeover of an existing client sessions is now treated as a _clean_ disconnect of the previous client. diff --git a/Build/build.ps1 b/Build/build.ps1 index 021858f7..1009db00 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -26,10 +26,18 @@ if ($path) { &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue + # Build the Managed Client extension + &$msbuild ..\Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + + 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 } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ApplicationMessageProcessedEventArgs.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs similarity index 93% rename from Frameworks/MQTTnet.NetStandard/ManagedClient/ApplicationMessageProcessedEventArgs.cs rename to Extensions/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs index fa82c0a4..6bc70046 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ApplicationMessageProcessedEventArgs.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs @@ -1,6 +1,6 @@ using System; -namespace MQTTnet.ManagedClient +namespace MQTTnet.Extensions.ManagedClient { public class ApplicationMessageProcessedEventArgs : EventArgs { diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClient.cs b/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs similarity index 94% rename from Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClient.cs rename to Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs index 97cdeca9..1e15daaf 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClient.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using MQTTnet.Client; -namespace MQTTnet.ManagedClient +namespace MQTTnet.Extensions.ManagedClient { public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable { diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientOptions.cs b/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs similarity index 84% rename from Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientOptions.cs rename to Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs index fbb87b79..77736c44 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientOptions.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs @@ -1,7 +1,7 @@ using System; using MQTTnet.Client; -namespace MQTTnet.ManagedClient +namespace MQTTnet.Extensions.ManagedClient { public interface IManagedMqttClientOptions { diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientStorage.cs b/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs similarity index 86% rename from Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientStorage.cs rename to Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs index 28bf5703..2127054e 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientStorage.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace MQTTnet.ManagedClient +namespace MQTTnet.Extensions.ManagedClient { public interface IManagedMqttClientStorage { diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj b/Extensions/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj new file mode 100644 index 00000000..64bbf92c --- /dev/null +++ b/Extensions/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj @@ -0,0 +1,31 @@ + + + + netstandard1.3;netstandard2.0;net452;net461;uap10.0 + 0.0.0.0 + 0.0.0.0 + + + + + 0.0.0.0 + + + + false + UAP,Version=v10.0 + UAP + 10.0.17134.0 + 10.0.10240.0 + .NETCore + v5.0 + $(DefineConstants);WINDOWS_UWP + en + $(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets + + + + + + + diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessage.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessage.cs new file mode 100644 index 00000000..bba73b0c --- /dev/null +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessage.cs @@ -0,0 +1,11 @@ +using System; + +namespace MQTTnet.Extensions.ManagedClient +{ + public class ManagedMqttApplicationMessage + { + public Guid Id { get; set; } = Guid.NewGuid(); + + public MqttApplicationMessage ApplicationMessage { get; set; } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs similarity index 99% rename from Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs rename to Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 804b1861..0303cafc 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -10,7 +10,7 @@ using MQTTnet.Exceptions; using MQTTnet.Internal; using MQTTnet.Protocol; -namespace MQTTnet.ManagedClient +namespace MQTTnet.Extensions.ManagedClient { public class ManagedMqttClient : IManagedMqttClient { diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientExtensions.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs similarity index 97% rename from Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientExtensions.cs rename to Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs index 84b37858..ba5a16f7 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientExtensions.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using MQTTnet.Protocol; -namespace MQTTnet.ManagedClient +namespace MQTTnet.Extensions.ManagedClient { public static class ManagedMqttClientExtensions { diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptions.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs similarity index 88% rename from Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptions.cs rename to Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs index 813aee8a..e07e2d68 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptions.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs @@ -1,7 +1,7 @@ using System; using MQTTnet.Client; -namespace MQTTnet.ManagedClient +namespace MQTTnet.Extensions.ManagedClient { public class ManagedMqttClientOptions : IManagedMqttClientOptions { diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptionsBuilder.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs similarity index 98% rename from Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptionsBuilder.cs rename to Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs index 4d65bdbb..ef774c59 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptionsBuilder.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs @@ -1,7 +1,7 @@ using System; using MQTTnet.Client; -namespace MQTTnet.ManagedClient +namespace MQTTnet.Extensions.ManagedClient { public class ManagedMqttClientOptionsBuilder { diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs similarity index 97% rename from Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs rename to Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs index 49eaae34..4d13c33c 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Internal; -namespace MQTTnet.ManagedClient +namespace MQTTnet.Extensions.ManagedClient { public class ManagedMqttClientStorageManager { diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs b/Extensions/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs new file mode 100644 index 00000000..d2ad020d --- /dev/null +++ b/Extensions/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs @@ -0,0 +1,24 @@ +using System; +using MQTTnet.Client; +using MQTTnet.Diagnostics; + +namespace MQTTnet.Extensions.ManagedClient +{ + public static class MqttFactoryExtensions + { + public static IManagedMqttClient CreateManagedMqttClient(this IMqttClientFactory factory) + { + if (factory == null) throw new ArgumentNullException(nameof(factory)); + + return new ManagedMqttClient(factory.CreateMqttClient(), new MqttNetLogger().CreateChildLogger()); + } + + public static IManagedMqttClient CreateManagedMqttClient(this IMqttClientFactory factory, IMqttNetLogger logger) + { + if (factory == null) throw new ArgumentNullException(nameof(factory)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + return new ManagedMqttClient(factory.CreateMqttClient(), logger.CreateChildLogger()); + } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ReconnectionResult.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs similarity index 71% rename from Frameworks/MQTTnet.NetStandard/ManagedClient/ReconnectionResult.cs rename to Extensions/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs index 42566e3e..fa876c36 100644 --- a/Frameworks/MQTTnet.NetStandard/ManagedClient/ReconnectionResult.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs @@ -1,4 +1,4 @@ -namespace MQTTnet.ManagedClient +namespace MQTTnet.Extensions.ManagedClient { public enum ReconnectionResult { diff --git a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientFactory.cs b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientFactory.cs index 444c16fc..a15b499b 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientFactory.cs @@ -1,5 +1,4 @@ using MQTTnet.Diagnostics; -using MQTTnet.ManagedClient; namespace MQTTnet.Client { @@ -8,9 +7,5 @@ namespace MQTTnet.Client IMqttClient CreateMqttClient(); IMqttClient CreateMqttClient(IMqttNetLogger logger); - - IManagedMqttClient CreateManagedMqttClient(); - - IManagedMqttClient CreateManagedMqttClient(IMqttNetLogger logger); } } \ No newline at end of file From c8bf36044b5364856edc006cefd181adbc37cfdf Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Jun 2018 21:28:39 +0200 Subject: [PATCH 086/138] Rerfactor package parsing. --- .../Adapter/MqttChannelAdapter.cs | 75 ++++++++-------- .../Adapter/ReceivedMqttPacket.cs | 14 +-- .../Client/MqttClientTcpOptions.cs | 5 ++ .../Client/MqttClientWebSocketOptions.cs | 7 +- .../Internal/TestMqttChannel.cs | 2 +- Frameworks/MQTTnet.NetStandard/MqttFactory.cs | 13 --- .../Serializer/Extensions.cs | 20 +++++ .../Serializer/MqttFixedHeader.cs | 4 +- .../Serializer/MqttPacketBodyReader.cs | 54 ++++++++++++ .../Serializer/MqttPacketReader.cs | 86 +++++-------------- .../Serializer/MqttPacketSerializer.cs | 32 +++---- .../Server/MqttClientSession.cs | 9 +- .../Server/MqttClientSessionsManager.cs | 12 ++- 13 files changed, 177 insertions(+), 156 deletions(-) create mode 100644 Frameworks/MQTTnet.NetStandard/Serializer/Extensions.cs create mode 100644 Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketBodyReader.cs diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 73588efa..218fcb93 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -97,35 +97,29 @@ namespace MQTTnet.Adapter MqttBasePacket packet = null; await ExecuteAndWrapExceptionAsync(async () => { - ReceivedMqttPacket receivedMqttPacket = null; - try + ReceivedMqttPacket receivedMqttPacket; + + if (timeout > TimeSpan.Zero) { - if (timeout > TimeSpan.Zero) - { - receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfter(ct => ReceiveAsync(_channel, ct), timeout, cancellationToken).ConfigureAwait(false); - } - else - { - receivedMqttPacket = await ReceiveAsync(_channel, cancellationToken).ConfigureAwait(false); - } - - if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested) - { - throw new TaskCanceledException(); - } - - packet = PacketSerializer.Deserialize(receivedMqttPacket); - if (packet == null) - { - throw new MqttProtocolViolationException("Received malformed packet."); - } - - _logger.Verbose("RX <<< {0}", packet); + receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfter(ct => ReceiveAsync(_channel, ct), timeout, cancellationToken).ConfigureAwait(false); } - finally + else { - receivedMqttPacket?.Dispose(); + receivedMqttPacket = await ReceiveAsync(_channel, cancellationToken).ConfigureAwait(false); } + + if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested) + { + return; + } + + packet = PacketSerializer.Deserialize(receivedMqttPacket); + if (packet == null) + { + throw new MqttProtocolViolationException("Received malformed packet."); + } + + _logger.Verbose("RX <<< {0}", packet); }).ConfigureAwait(false); return packet; @@ -134,7 +128,11 @@ namespace MQTTnet.Adapter private async Task ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken) { var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, cancellationToken).ConfigureAwait(false); - + if (fixedHeader == null) + { + return null; + } + try { ReadingPacketStarted?.Invoke(this, EventArgs.Empty); @@ -144,31 +142,28 @@ namespace MQTTnet.Adapter return new ReceivedMqttPacket(fixedHeader.Flags, null); } - var body = new MemoryStream(fixedHeader.RemainingLength); + var body = new byte[fixedHeader.RemainingLength]; + var bodyOffset = 0; + var chunkSize = Math.Min(ReadBufferSize, fixedHeader.RemainingLength); - var buffer = new byte[Math.Min(ReadBufferSize, fixedHeader.RemainingLength)]; - while (body.Length < fixedHeader.RemainingLength) + do { - var bytesLeft = fixedHeader.RemainingLength - (int)body.Length; - if (bytesLeft > buffer.Length) + var bytesLeft = body.Length - bodyOffset; + if (chunkSize > bytesLeft) { - bytesLeft = buffer.Length; + chunkSize = bytesLeft; } - var readBytes = await channel.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false); + var readBytes = await channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken) .ConfigureAwait(false); if (readBytes <= 0) { ExceptionHelper.ThrowGracefulSocketClose(); } - // Here is no need to await because internally only an array is used and no real I/O operation is made. - // Using async here will only generate overhead. - body.Write(buffer, 0, readBytes); - } + bodyOffset += readBytes; + } while (bodyOffset < body.Length); - body.Seek(0L, SeekOrigin.Begin); - - return new ReceivedMqttPacket(fixedHeader.Flags, body); + return new ReceivedMqttPacket(fixedHeader.Flags, new MqttPacketBodyReader(body)); } finally { diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs b/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs index 1f1c59a9..3a92d4de 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs @@ -1,11 +1,10 @@ -using System; -using System.IO; +using MQTTnet.Serializer; namespace MQTTnet.Adapter { - public class ReceivedMqttPacket : IDisposable + public class ReceivedMqttPacket { - public ReceivedMqttPacket(byte fixedHeader, MemoryStream body) + public ReceivedMqttPacket(byte fixedHeader, MqttPacketBodyReader body) { FixedHeader = fixedHeader; Body = body; @@ -13,11 +12,6 @@ namespace MQTTnet.Adapter public byte FixedHeader { get; } - public MemoryStream Body { get; } - - public void Dispose() - { - Body?.Dispose(); - } + public MqttPacketBodyReader Body { get; } } } diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs index 44bc5b44..4569be33 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs @@ -9,5 +9,10 @@ public int BufferSize { get; set; } = 4096; public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); + + public override string ToString() + { + return Server + ":" + this.GetPort(); + } } } diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs index 9718298a..a4dd0d5c 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs @@ -13,8 +13,11 @@ namespace MQTTnet.Client public CookieContainer CookieContainer { get; set; } - public int BufferSize { get; set; } = 4096; - public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); + + public override string ToString() + { + return Uri; + } } } diff --git a/Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs b/Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs index 21d0584b..e48b8ede 100644 --- a/Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Channel; -namespace MQTTnet.Core.Internal +namespace MQTTnet.Internal { public class TestMqttChannel : IMqttChannel { diff --git a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs index 0ba3d2f0..00438fa9 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs @@ -4,7 +4,6 @@ using MQTTnet.Adapter; using MQTTnet.Client; using MQTTnet.Diagnostics; using MQTTnet.Implementations; -using MQTTnet.ManagedClient; using MQTTnet.Server; namespace MQTTnet @@ -23,18 +22,6 @@ namespace MQTTnet return new MqttClient(new MqttClientAdapterFactory(), logger); } - public IManagedMqttClient CreateManagedMqttClient() - { - return new ManagedMqttClient(CreateMqttClient(), new MqttNetLogger().CreateChildLogger()); - } - - public IManagedMqttClient CreateManagedMqttClient(IMqttNetLogger logger) - { - if (logger == null) throw new ArgumentNullException(nameof(logger)); - - return new ManagedMqttClient(CreateMqttClient(), logger.CreateChildLogger()); - } - public IMqttServer CreateMqttServer() { var logger = new MqttNetLogger(); diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/Extensions.cs b/Frameworks/MQTTnet.NetStandard/Serializer/Extensions.cs new file mode 100644 index 00000000..1a14de4f --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Serializer/Extensions.cs @@ -0,0 +1,20 @@ +using System; + +namespace MQTTnet.Serializer +{ + public static class Extensions + { + public static byte[] ToArray(this ArraySegment source) + { + if (source.Array == null) + { + return null; + } + + var buffer = new byte[source.Count]; + Buffer.BlockCopy(source.Array, source.Offset, buffer, 0, buffer.Length); + + return buffer; + } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttFixedHeader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttFixedHeader.cs index a8c2015b..d87f63d4 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttFixedHeader.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttFixedHeader.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Serializer { - public struct MqttFixedHeader + public class MqttFixedHeader { public MqttFixedHeader(byte flags, int remainingLength) { @@ -10,6 +10,6 @@ public byte Flags { get; } - public int RemainingLength { get; } + public int RemainingLength { get; set; } } } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketBodyReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketBodyReader.cs new file mode 100644 index 00000000..04c0a222 --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketBodyReader.cs @@ -0,0 +1,54 @@ +using System; +using System.Text; + +namespace MQTTnet.Serializer +{ + public class MqttPacketBodyReader + { + private readonly byte[] _buffer; + private int _offset; + + public MqttPacketBodyReader(byte[] buffer) + { + _buffer = buffer; + } + + public int Length => _buffer.Length - _offset; + + public bool EndOfStream => _offset == _buffer.Length; + + public byte ReadByte() + { + return _buffer[_offset++]; + } + + public ArraySegment ReadRemainingData() + { + return new ArraySegment(_buffer, _offset, _buffer.Length - _offset); + } + + public ushort ReadUInt16() + { + var msb = _buffer[_offset++]; + var lsb = _buffer[_offset++]; + + return (ushort)(msb << 8 | lsb); + } + + public ArraySegment ReadWithLengthPrefix() + { + var length = ReadUInt16(); + + var result = new ArraySegment(_buffer, _offset, length); + _offset += length; + + return result; + } + + public string ReadStringWithLengthPrefix() + { + var buffer = ReadWithLengthPrefix(); + return Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count); + } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs index 110407e2..7ed918f6 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; -using System.Text; -using System.Threading; +using System.Threading; using System.Threading.Tasks; using MQTTnet.Channel; using MQTTnet.Exceptions; @@ -23,6 +20,11 @@ namespace MQTTnet.Serializer var bytesRead = await channel.ReadAsync(buffer, 0, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false); if (bytesRead <= 0) { + if (cancellationToken.IsCancellationRequested) + { + return null; + } + ExceptionHelper.ThrowGracefulSocketClose(); } @@ -35,78 +37,20 @@ namespace MQTTnet.Serializer return new MqttFixedHeader(buffer[0], 0); } - var bodyLength = await ReadBodyLengthAsync(channel, buffer[1], cancellationToken); + var bodyLength = await ReadBodyLengthAsync(channel, buffer[1], cancellationToken).ConfigureAwait(false); return new MqttFixedHeader(buffer[0], bodyLength); } - public static ushort ReadUInt16(this Stream stream) - { - var buffer = stream.ReadBytes(2); - - var temp = buffer[0]; - buffer[0] = buffer[1]; - buffer[1] = temp; - - return BitConverter.ToUInt16(buffer, 0); - } - - public static string ReadStringWithLengthPrefix(this Stream stream) - { - var buffer = stream.ReadWithLengthPrefix(); - if (buffer.Length == 0) - { - return string.Empty; - } - - return Encoding.UTF8.GetString(buffer, 0, buffer.Length); - } - - public static byte[] ReadWithLengthPrefix(this Stream stream) - { - var length = stream.ReadUInt16(); - if (length == 0) - { - return new byte[0]; - } - - return stream.ReadBytes(length); - } - - public static byte[] ReadRemainingData(this Stream stream) - { - return stream.ReadBytes((int)(stream.Length - stream.Position)); - } - - private static byte[] ReadBytes(this Stream stream, int count) - { - var buffer = new byte[count]; - var readBytes = stream.Read(buffer, 0, count); - - if (readBytes != count) - { - throw new InvalidOperationException($"Unable to read {count} bytes from the stream."); - } - - return buffer; - } - private static async Task ReadBodyLengthAsync(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; var value = initialEncodedByte & 127; int encodedByte = initialEncodedByte; - var buffer = new byte[1]; - + while ((encodedByte & 128) != 0) { - var readCount = await channel.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); - if (readCount <= 0) - { - ExceptionHelper.ThrowGracefulSocketClose(); - } - - encodedByte = buffer[0]; + encodedByte = await ReadByteAsync(channel, cancellationToken).ConfigureAwait(false); value += (byte)(encodedByte & 127) * multiplier; if (multiplier > 128 * 128 * 128) @@ -119,5 +63,17 @@ namespace MQTTnet.Serializer return value; } + + private static async Task ReadByteAsync(IMqttChannel channel, CancellationToken cancellationToken) + { + var buffer = new byte[1]; + var readCount = await channel.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); + if (readCount <= 0) + { + ExceptionHelper.ThrowGracefulSocketClose(); + } + + return buffer[0]; + } } } diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs index 32001db3..2acbfbbc 100644 --- a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs +++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs @@ -104,7 +104,7 @@ namespace MQTTnet.Serializer } } - private static MqttBasePacket DeserializeUnsubAck(Stream body) + private static MqttBasePacket DeserializeUnsubAck(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -114,7 +114,7 @@ namespace MQTTnet.Serializer }; } - private static MqttBasePacket DeserializePubComp(Stream body) + private static MqttBasePacket DeserializePubComp(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -124,7 +124,7 @@ namespace MQTTnet.Serializer }; } - private static MqttBasePacket DeserializePubRel(Stream body) + private static MqttBasePacket DeserializePubRel(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -134,7 +134,7 @@ namespace MQTTnet.Serializer }; } - private static MqttBasePacket DeserializePubRec(Stream body) + private static MqttBasePacket DeserializePubRec(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -144,7 +144,7 @@ namespace MQTTnet.Serializer }; } - private static MqttBasePacket DeserializePubAck(Stream body) + private static MqttBasePacket DeserializePubAck(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -154,7 +154,7 @@ namespace MQTTnet.Serializer }; } - private static MqttBasePacket DeserializeUnsubscribe(Stream body) + private static MqttBasePacket DeserializeUnsubscribe(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -163,7 +163,7 @@ namespace MQTTnet.Serializer PacketIdentifier = body.ReadUInt16(), }; - while (body.Position != body.Length) + while (!body.EndOfStream) { packet.TopicFilters.Add(body.ReadStringWithLengthPrefix()); } @@ -171,7 +171,7 @@ namespace MQTTnet.Serializer return packet; } - private static MqttBasePacket DeserializeSubscribe(Stream body) + private static MqttBasePacket DeserializeSubscribe(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -180,7 +180,7 @@ namespace MQTTnet.Serializer PacketIdentifier = body.ReadUInt16() }; - while (body.Position != body.Length) + while (!body.EndOfStream) { packet.TopicFilters.Add(new TopicFilter( body.ReadStringWithLengthPrefix(), @@ -213,7 +213,7 @@ namespace MQTTnet.Serializer PacketIdentifier = packetIdentifier, Retain = retain, Topic = topic, - Payload = body.ReadRemainingData(), + Payload = body.ReadRemainingData().ToArray(), QualityOfServiceLevel = qualityOfServiceLevel, Dup = dup }; @@ -221,7 +221,7 @@ namespace MQTTnet.Serializer return packet; } - private static MqttBasePacket DeserializeConnect(Stream body) + private static MqttBasePacket DeserializeConnect(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -279,7 +279,7 @@ namespace MQTTnet.Serializer packet.WillMessage = new MqttApplicationMessage { Topic = body.ReadStringWithLengthPrefix(), - Payload = body.ReadWithLengthPrefix(), + Payload = body.ReadWithLengthPrefix().ToArray(), QualityOfServiceLevel = (MqttQualityOfServiceLevel)willQoS, Retain = willRetain }; @@ -299,7 +299,7 @@ namespace MQTTnet.Serializer return packet; } - private static MqttBasePacket DeserializeSubAck(Stream body) + private static MqttBasePacket DeserializeSubAck(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -308,7 +308,7 @@ namespace MQTTnet.Serializer PacketIdentifier = body.ReadUInt16() }; - while (body.Position != body.Length) + while (!body.EndOfStream) { packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)body.ReadByte()); } @@ -316,7 +316,7 @@ namespace MQTTnet.Serializer return packet; } - private MqttBasePacket DeserializeConnAck(Stream body) + private MqttBasePacket DeserializeConnAck(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -614,7 +614,7 @@ namespace MQTTnet.Serializer return MqttPacketWriter.BuildFixedHeader(type); } - private static void ThrowIfBodyIsEmpty(Stream body) + private static void ThrowIfBodyIsEmpty(MqttPacketBodyReader body) { if (body == null || body.Length == 0) { diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs index 4fd72667..cb1043be 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs @@ -86,8 +86,11 @@ namespace MQTTnet.Server while (!_cancellationTokenSource.IsCancellationRequested) { var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false); - _keepAliveMonitor.PacketReceived(packet); - await ProcessReceivedPacketAsync(adapter, packet, _cancellationTokenSource.Token).ConfigureAwait(false); + if (packet != null) + { + _keepAliveMonitor.PacketReceived(packet); + await ProcessReceivedPacketAsync(adapter, packet, _cancellationTokenSource.Token).ConfigureAwait(false); + } } } catch (OperationCanceledException) @@ -351,7 +354,7 @@ namespace MQTTnet.Server private Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) { - // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] + // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage); var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs index 692e9c6c..dadf32a2 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs @@ -41,11 +41,15 @@ namespace MQTTnet.Server try { - if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken) - .ConfigureAwait(false) is MqttConnectPacket connectPacket)) + var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); + if (firstPacket == null) { - throw new MqttProtocolViolationException( - "The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); + return; + } + + if (!(firstPacket is MqttConnectPacket connectPacket)) + { + throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); } clientId = connectPacket.ClientId; From ae72bc3f994d9d517b0f3df631a4e4a561aaea8f Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Jun 2018 21:29:19 +0200 Subject: [PATCH 087/138] Fix wrong exceptions during a clean disconnect. --- .../MQTTnet.NetStandard/Client/MqttClient.cs | 66 ++++++++++++------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs index eeb3d333..d93a356d 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs +++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs @@ -28,6 +28,7 @@ namespace MQTTnet.Client private Task _packetReceiverTask; private Task _keepAliveMessageSenderTask; private IMqttChannelAdapter _adapter; + private bool _cleanDisconnectInitiated; public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger) { @@ -59,7 +60,7 @@ namespace MQTTnet.Client _adapter = _adapterFactory.CreateClientAdapter(options, _logger); - _logger.Verbose("Trying to connect with server."); + _logger.Verbose($"Trying to connect with server ({_options.ChannelOptions})."); await _adapter.ConnectAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false); _logger.Verbose("Connection with server established."); @@ -94,6 +95,8 @@ namespace MQTTnet.Client { try { + _cleanDisconnectInitiated = true; + if (IsConnected && !_cancellationTokenSource.IsCancellationRequested) { await SendAsync(new MqttDisconnectPacket(), _cancellationTokenSource.Token).ConfigureAwait(false); @@ -235,24 +238,7 @@ namespace MQTTnet.Client private async Task DisconnectInternalAsync(Task sender, Exception exception) { - await _disconnectLock.WaitAsync().ConfigureAwait(false); - try - { - if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested) - { - return; - } - - _cancellationTokenSource.Cancel(false); - } - catch (Exception adapterException) - { - _logger.Warning(adapterException, "Error while disconnecting from adapter."); - } - finally - { - _disconnectLock.Release(); - } + await InitiateDisconnectAsync().ConfigureAwait(false); var clientWasConnected = IsConnected; IsConnected = false; @@ -279,12 +265,35 @@ namespace MQTTnet.Client _adapter = null; _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; + _cleanDisconnectInitiated = false; _logger.Info("Disconnected."); Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception)); } } + private async Task InitiateDisconnectAsync() + { + await _disconnectLock.WaitAsync().ConfigureAwait(false); + try + { + if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested) + { + return; + } + + _cancellationTokenSource.Cancel(false); + } + catch (Exception adapterException) + { + _logger.Warning(adapterException, "Error while initiating disconnect."); + } + finally + { + _disconnectLock.Release(); + } + } + private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken) { return SendAsync(new[] { packet }, cancellationToken); @@ -361,6 +370,11 @@ namespace MQTTnet.Client } catch (Exception exception) { + if (_cleanDisconnectInitiated) + { + return; + } + if (exception is OperationCanceledException) { } @@ -390,11 +404,19 @@ namespace MQTTnet.Client while (!cancellationToken.IsCancellationRequested) { var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); - await ProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false); + if (packet != null) + { + await ProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false); + } } } catch (Exception exception) { + if (_cleanDisconnectInitiated) + { + return; + } + if (exception is OperationCanceledException) { } @@ -407,8 +429,8 @@ namespace MQTTnet.Client _logger.Error(exception, "Unhandled exception while receiving packets."); } - await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false); _packetDispatcher.Dispatch(exception); + await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false); } finally { @@ -458,7 +480,7 @@ namespace MQTTnet.Client if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) { - // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] + // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) FireApplicationMessageReceivedEvent(publishPacket); return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, cancellationToken); } From 3480d9ac357c95409d373e9570fa2fdc19911299 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Jun 2018 21:29:34 +0200 Subject: [PATCH 088/138] Refactoring --- MQTTnet.sln | 22 +++++++++ README.md | 2 +- .../ChannelAdapterBenchmark.cs | 1 - .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 3 +- .../Tcp/BufferExtensions.cs | 4 +- .../MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs | 4 +- .../MQTTnet.Benchmarks/Tcp/SocketReceiver.cs | 1 - Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs | 1 - Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs | 1 - .../MqttPacketReaderTests.cs | 2 +- .../MqttPacketSerializerTests.cs | 4 +- .../MQTTnet.TestApp.NetCore/ClientFlowTest.cs | 47 +++++++++++++++++++ .../MQTTnet.TestApp.NetCore.csproj | 1 + .../ManagedClientTest.cs | 2 +- Tests/MQTTnet.TestApp.NetCore/Program.cs | 5 ++ .../MQTTnet.TestApp.UniversalWindows.csproj | 4 ++ .../MainPage.xaml.cs | 3 +- 17 files changed, 88 insertions(+), 19 deletions(-) create mode 100644 Tests/MQTTnet.TestApp.NetCore/ClientFlowTest.cs diff --git a/MQTTnet.sln b/MQTTnet.sln index 6cb185db..9e3f0778 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -18,12 +18,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1 appveyor.yml = appveyor.yml Build\build.ps1 = Build\build.ps1 Build\MQTTnet.AspNetCore.nuspec = Build\MQTTnet.AspNetCore.nuspec + 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 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}" ProjectSection(SolutionItems) = preProject + .bettercodehub.yml = .bettercodehub.yml + appveyor.yml = appveyor.yml LICENSE = LICENSE README.md = README.md EndProjectSection @@ -40,6 +43,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "E EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Tests\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{998D04DD-7CB0-45F5-A393-E2495C16399E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedClient", "Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj", "{C400533A-8EBA-4F0B-BF4D-295C3708604B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -186,6 +191,22 @@ Global {998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|x64.Build.0 = Release|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|x86.ActiveCfg = Release|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|x86.Build.0 = Release|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|ARM.ActiveCfg = Debug|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|ARM.Build.0 = Debug|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|x64.ActiveCfg = Debug|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|x64.Build.0 = Debug|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|x86.ActiveCfg = Debug|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Debug|x86.Build.0 = Debug|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|Any CPU.Build.0 = Release|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|ARM.ActiveCfg = Release|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|ARM.Build.0 = Release|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|x64.ActiveCfg = Release|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|x64.Build.0 = Release|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|x86.ActiveCfg = Release|Any CPU + {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -199,6 +220,7 @@ Global {F10C4060-F7EE-4A83-919F-FF723E72F94A} = {32A630A7-2598-41D7-B625-204CD906F5FB} {C444E9C8-95FA-430E-9126-274129DE16CD} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587} {998D04DD-7CB0-45F5-A393-E2495C16399E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} + {C400533A-8EBA-4F0B-BF4D-295C3708604B} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} diff --git a/README.md b/README.md index cff5490f..52735cae 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * Performance optimized (processing ~60.000 messages / second)* * Interfaces included for mocking and testing * Access to internal trace messages -* Unit tested (~80 tests) +* Unit tested (~90 tests) \* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_. diff --git a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs index 726f5e0f..577d6960 100644 --- a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -1,6 +1,5 @@ using BenchmarkDotNet.Attributes; using MQTTnet.Adapter; -using MQTTnet.Core.Internal; using MQTTnet.Diagnostics; using MQTTnet.Internal; using MQTTnet.Packets; diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index 389998f6..06e7b397 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -8,7 +8,6 @@ using System; using System.Threading; using System.IO; using MQTTnet.Adapter; -using MQTTnet.Core.Internal; namespace MQTTnet.Benchmarks { @@ -55,7 +54,7 @@ namespace MQTTnet.Benchmarks using (var bodyStream = new MemoryStream(Join(_serializedPacket), (int)headerStream.Position, header.RemainingLength)) { - _serializer.Deserialize(new ReceivedMqttPacket(header.Flags, bodyStream)); + _serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray()))); } } } diff --git a/Tests/MQTTnet.Benchmarks/Tcp/BufferExtensions.cs b/Tests/MQTTnet.Benchmarks/Tcp/BufferExtensions.cs index 0a2c19e8..879306c1 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/BufferExtensions.cs +++ b/Tests/MQTTnet.Benchmarks/Tcp/BufferExtensions.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; using System.Runtime.InteropServices; -using System.Text; -namespace Playground.Client.Mqtt.Tcp +namespace MQTTnet.Benchmarks.Tcp { public static class BufferExtensions { diff --git a/Tests/MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs b/Tests/MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs index dcaaaf41..2271bd76 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs +++ b/Tests/MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs @@ -1,14 +1,12 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipelines; using System.Net.Sockets; using System.Runtime.CompilerServices; -using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Playground.Client.Mqtt.Tcp +namespace MQTTnet.Benchmarks.Tcp { public class SocketAwaitable : ICriticalNotifyCompletion { diff --git a/Tests/MQTTnet.Benchmarks/Tcp/SocketReceiver.cs b/Tests/MQTTnet.Benchmarks/Tcp/SocketReceiver.cs index e2f19c07..bc8e5c03 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/SocketReceiver.cs +++ b/Tests/MQTTnet.Benchmarks/Tcp/SocketReceiver.cs @@ -1,7 +1,6 @@ using System; using System.IO.Pipelines; using System.Net.Sockets; -using Playground.Client.Mqtt.Tcp; namespace MQTTnet.Benchmarks.Tcp { diff --git a/Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs b/Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs index 475fa685..7cb1bc1d 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs +++ b/Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipelines; using System.Net.Sockets; -using Playground.Client.Mqtt.Tcp; namespace MQTTnet.Benchmarks.Tcp { diff --git a/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs b/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs index e73067c7..96b1d858 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs +++ b/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs @@ -6,7 +6,6 @@ using System.Net; using System.Net.Sockets; using System.Threading.Tasks; using MQTTnet.Exceptions; -using Playground.Client.Mqtt.Tcp; namespace MQTTnet.Benchmarks.Tcp { diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs index 58ef6b11..e3e1ccd3 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs @@ -1,8 +1,8 @@ using System.IO; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Core.Internal; using MQTTnet.Exceptions; +using MQTTnet.Internal; using MQTTnet.Serializer; namespace MQTTnet.Core.Tests diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 6ba3b124..40aa9535 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Adapter; -using MQTTnet.Core.Internal; +using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; using MQTTnet.Serializer; @@ -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, bodyStream)); + var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray()))); var buffer2 = serializer.Serialize(deserializedPacket); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2))); diff --git a/Tests/MQTTnet.TestApp.NetCore/ClientFlowTest.cs b/Tests/MQTTnet.TestApp.NetCore/ClientFlowTest.cs new file mode 100644 index 00000000..4b166236 --- /dev/null +++ b/Tests/MQTTnet.TestApp.NetCore/ClientFlowTest.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Tasks; +using MQTTnet.Client; + +namespace MQTTnet.TestApp.NetCore +{ + public static class ClientFlowTest + { + public static async Task RunAsync() + { + MqttNetConsoleLogger.ForwardToConsole(); + try + { + var factory = new MqttFactory(); + var client = factory.CreateMqttClient(); + + var options = new MqttClientOptionsBuilder() + .WithTcpServer("localhost") + .Build(); + + Console.WriteLine("BEFORE CONNECT"); + await client.ConnectAsync(options); + Console.WriteLine("AFTER CONNECT"); + + Console.WriteLine("BEFORE SUBSCRIBE"); + await client.SubscribeAsync("test/topic"); + Console.WriteLine("AFTER SUBSCRIBE"); + + Console.WriteLine("BEFORE PUBLISH"); + await client.PublishAsync("test/topic", "payload"); + Console.WriteLine("AFTER PUBLISH"); + + await Task.Delay(1000); + + Console.WriteLine("BEFORE DISCONNECT"); + await client.DisconnectAsync(); + Console.WriteLine("AFTER DISCONNECT"); + + Console.WriteLine("FINISHED"); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + } +} diff --git a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj index 43fd8c87..cf1aa34e 100644 --- a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj +++ b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj @@ -15,6 +15,7 @@ + diff --git a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs index 4ecca0ff..8efdebe0 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs @@ -4,7 +4,7 @@ using System.IO; using Newtonsoft.Json; using System.Collections.Generic; using MQTTnet.Client; -using MQTTnet.ManagedClient; +using MQTTnet.Extensions.ManagedClient; using MQTTnet.Protocol; namespace MQTTnet.TestApp.NetCore diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 270b16ed..f8a5d275 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -21,6 +21,7 @@ namespace MQTTnet.TestApp.NetCore Console.WriteLine("4 = Start managed client"); Console.WriteLine("5 = Start public broker test"); Console.WriteLine("6 = Start server & client"); + Console.WriteLine("7 = Client flow test"); var pressedKey = Console.ReadKey(true); if (pressedKey.KeyChar == '1') @@ -47,6 +48,10 @@ namespace MQTTnet.TestApp.NetCore { Task.Run(ServerAndClientTest.RunAsync); } + else if (pressedKey.KeyChar == '7') + { + Task.Run(ClientFlowTest.RunAsync); + } Thread.Sleep(Timeout.Infinite); } diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj index 0a69b342..3bf7ee80 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj @@ -127,6 +127,10 @@ + + {C400533A-8EBA-4F0B-BF4D-295C3708604B} + MQTTnet.Extensions.ManagedClient + {c444e9c8-95fa-430e-9126-274129de16cd} MQTTnet.Extensions.Rpc diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 52db69e3..76f947d4 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -8,9 +8,9 @@ using Windows.UI.Xaml; using MQTTnet.Client; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; +using MQTTnet.Extensions.ManagedClient; using MQTTnet.Extensions.Rpc; using MQTTnet.Implementations; -using MQTTnet.ManagedClient; using MQTTnet.Protocol; using MQTTnet.Server; using MqttClientConnectedEventArgs = MQTTnet.Client.MqttClientConnectedEventArgs; @@ -613,7 +613,6 @@ namespace MQTTnet.TestApp.UniversalWindows await mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("my/topic").Build()); await mqttClient.StartAsync(options); } - } } } From 78360bc24ab2772fd44b4d47e22a8e0ec2a42950 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Jun 2018 21:44:25 +0200 Subject: [PATCH 089/138] Add ID property for the ManagedMqttApplicationMessage. --- Build/MQTTnet.nuspec | 2 +- .../ApplicationMessageProcessedEventArgs.cs | 4 +- .../IManagedMqttClient.cs | 2 + .../IManagedMqttClientStorage.cs | 4 +- .../ManagedMqttApplicationMessageBuilder.cs | 47 +++++++++++++++++++ .../ManagedMqttClient.cs | 24 ++++++---- .../ManagedMqttClientStorageManager.cs | 8 ++-- .../ManagedClientTest.cs | 10 ++-- 8 files changed, 79 insertions(+), 22 deletions(-) create mode 100644 Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessageBuilder.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 8e09d921..71ec12d4 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -19,7 +19,7 @@ * [Client] Fixed wrong calculation for sending keep alive packets (thanks to @cstichlberger) * [Client] A clean disconnect (via DisconnectAsync) will no longer throw an exception. * [ManagedClient] The managed client is moved to a separate nuget package. -* [ManagedClient] Added an own message format with extended properties like ID. +* [ManagedClient] Added an own message format with extended properties like ID (BREAKING CHANGE). * [ManagedClient] Fixed a loading issue of stored application messages (thanks to @JTrotta). * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot). * [Server] The takeover of an existing client sessions is now treated as a _clean_ disconnect of the previous client. diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs index 6bc70046..6eb97d23 100644 --- a/Extensions/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs @@ -4,13 +4,13 @@ namespace MQTTnet.Extensions.ManagedClient { public class ApplicationMessageProcessedEventArgs : EventArgs { - public ApplicationMessageProcessedEventArgs(MqttApplicationMessage applicationMessage, Exception exception) + public ApplicationMessageProcessedEventArgs(ManagedMqttApplicationMessage applicationMessage, Exception exception) { ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); Exception = exception; } - public MqttApplicationMessage ApplicationMessage { get; } + public ManagedMqttApplicationMessage ApplicationMessage { get; } public Exception Exception { get; } public bool HasFailed => Exception != null; diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs b/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs index 1e15daaf..ff9f5303 100644 --- a/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs @@ -20,5 +20,7 @@ namespace MQTTnet.Extensions.ManagedClient Task SubscribeAsync(IEnumerable topicFilters); Task UnsubscribeAsync(IEnumerable topics); + + Task PublishAsync(IEnumerable applicationMessages); } } \ No newline at end of file diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs b/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs index 2127054e..dc44001a 100644 --- a/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs @@ -5,8 +5,8 @@ namespace MQTTnet.Extensions.ManagedClient { public interface IManagedMqttClientStorage { - Task SaveQueuedMessagesAsync(IList messages); + Task SaveQueuedMessagesAsync(IList messages); - Task> LoadQueuedMessagesAsync(); + Task> LoadQueuedMessagesAsync(); } } diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessageBuilder.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessageBuilder.cs new file mode 100644 index 00000000..bf67f70e --- /dev/null +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessageBuilder.cs @@ -0,0 +1,47 @@ +using System; + +namespace MQTTnet.Extensions.ManagedClient +{ + public class ManagedMqttApplicationMessageBuilder + { + private Guid _id = Guid.NewGuid(); + private MqttApplicationMessage _applicationMessage; + + public ManagedMqttApplicationMessageBuilder WithId(Guid id) + { + _id = id; + return this; + } + + public ManagedMqttApplicationMessageBuilder WithApplicationMessage(MqttApplicationMessage applicationMessage) + { + _applicationMessage = applicationMessage; + return this; + } + + public ManagedMqttApplicationMessageBuilder WithApplicationMessage(Action builder) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + var internalBuilder = new MqttApplicationMessageBuilder(); + builder(internalBuilder); + + _applicationMessage = internalBuilder.Build(); + return this; + } + + public ManagedMqttApplicationMessage Build() + { + if (_applicationMessage == null) + { + throw new InvalidOperationException("The ApplicationMessage cannot be null."); + } + + return new ManagedMqttApplicationMessage + { + Id = _id, + ApplicationMessage = _applicationMessage + }; + } + } +} diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 0303cafc..a9693a80 100644 --- a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -14,7 +14,7 @@ namespace MQTTnet.Extensions.ManagedClient { public class ManagedMqttClient : IManagedMqttClient { - private readonly BlockingCollection _messageQueue = new BlockingCollection(); + private readonly BlockingCollection _messageQueue = new BlockingCollection(); private readonly Dictionary _subscriptions = new Dictionary(); private readonly AsyncLock _subscriptionsLock = new AsyncLock(); private readonly List _unsubscriptions = new List(); @@ -99,7 +99,15 @@ namespace MQTTnet.Extensions.ManagedClient return Task.FromResult(0); } - public async Task PublishAsync(IEnumerable applicationMessages) + public Task PublishAsync(IEnumerable applicationMessages) + { + if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); + + return PublishAsync(applicationMessages.Select(m => + new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(m).Build())); + } + + public async Task PublishAsync(IEnumerable applicationMessages) { if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); @@ -244,12 +252,12 @@ namespace MQTTnet.Extensions.ManagedClient } } - private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message) + private async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message) { Exception transmitException = null; try { - await _mqttClient.PublishAsync(message).ConfigureAwait(false); + await _mqttClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false); if (_storageManager != null) { @@ -260,9 +268,9 @@ namespace MQTTnet.Extensions.ManagedClient { transmitException = exception; - _logger.Warning(exception, "Publishing application message failed."); + _logger.Warning(exception, $"Publishing application ({message.Id}) message failed."); - if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) + if (message.ApplicationMessage.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { _messageQueue.Add(message); } @@ -270,7 +278,7 @@ namespace MQTTnet.Extensions.ManagedClient catch (Exception exception) { transmitException = exception; - _logger.Error(exception, "Unhandled exception while publishing queued application message."); + _logger.Error(exception, $"Unhandled exception while publishing application message ({message.Id})."); } finally { @@ -294,7 +302,7 @@ namespace MQTTnet.Extensions.ManagedClient _subscriptionsNotPushed = false; } - + if (!subscriptions.Any() && !unsubscriptions.Any()) { return; diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs index 4d13c33c..0f4e33e3 100644 --- a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs +++ b/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs @@ -8,7 +8,7 @@ namespace MQTTnet.Extensions.ManagedClient { public class ManagedMqttClientStorageManager { - private readonly List _messages = new List(); + private readonly List _messages = new List(); private readonly AsyncLock _messagesLock = new AsyncLock(); private readonly IManagedMqttClientStorage _storage; @@ -18,7 +18,7 @@ namespace MQTTnet.Extensions.ManagedClient _storage = storage ?? throw new ArgumentNullException(nameof(storage)); } - public async Task> LoadQueuedMessagesAsync() + public async Task> LoadQueuedMessagesAsync() { var loadedMessages = await _storage.LoadQueuedMessagesAsync().ConfigureAwait(false); _messages.AddRange(loadedMessages); @@ -26,7 +26,7 @@ namespace MQTTnet.Extensions.ManagedClient return _messages; } - public async Task AddAsync(MqttApplicationMessage applicationMessage) + public async Task AddAsync(ManagedMqttApplicationMessage applicationMessage) { using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { @@ -35,7 +35,7 @@ namespace MQTTnet.Extensions.ManagedClient } } - public async Task RemoveAsync(MqttApplicationMessage applicationMessage) + public async Task RemoveAsync(ManagedMqttApplicationMessage applicationMessage) { using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) { diff --git a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs index 8efdebe0..60ce27d7 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs @@ -76,23 +76,23 @@ namespace MQTTnet.TestApp.NetCore { private const string Filename = @"RetainedMessages.json"; - public Task SaveQueuedMessagesAsync(IList messages) + public Task SaveQueuedMessagesAsync(IList messages) { File.WriteAllText(Filename, JsonConvert.SerializeObject(messages)); return Task.FromResult(0); } - public Task> LoadQueuedMessagesAsync() + public Task> LoadQueuedMessagesAsync() { - IList retainedMessages; + IList retainedMessages; if (File.Exists(Filename)) { var json = File.ReadAllText(Filename); - retainedMessages = JsonConvert.DeserializeObject>(json); + retainedMessages = JsonConvert.DeserializeObject>(json); } else { - retainedMessages = new List(); + retainedMessages = new List(); } return Task.FromResult(retainedMessages); From ad778636cf272bc1497cef62f4b0d757983015d3 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 6 Jun 2018 21:45:58 +0200 Subject: [PATCH 090/138] Delete MQTTnet.NetStandard.csproj --- .../MQTTnet.NetStandard.csproj | 68 ------------------- 1 file changed, 68 deletions(-) delete mode 100644 Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj diff --git a/Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj b/Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj deleted file mode 100644 index 83643782..00000000 --- a/Frameworks/MQTTnet.Netstandard/MQTTnet.NetStandard.csproj +++ /dev/null @@ -1,68 +0,0 @@ - - - - netstandard1.3;netstandard2.0;net452;net461;uap10.0 - netstandard1.3;netstandard2.0 - MQTTnet - MQTTnet - False - 0.0.0.0 - 0.0.0.0 - 0.0.0.0 - - - - - - false - false - - - - false - UAP,Version=v10.0 - UAP - 10.0.17134.0 - 10.0.10240.0 - .NETCore - v5.0 - $(DefineConstants);WINDOWS_UWP - en - $(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets - - - - Full - - - - - - - - RELEASE;NETSTANDARD1_3 - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From a164c120d5c2d745a0f05c20066e42e778750870 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Jun 2018 21:47:59 +0200 Subject: [PATCH 091/138] Update solution file. --- MQTTnet.sln | 1 - 1 file changed, 1 deletion(-) diff --git a/MQTTnet.sln b/MQTTnet.sln index 9e3f0778..2e487b07 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -15,7 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.NetStandard", "Fram EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1-BC3A-420A-BE9C-FA2401431CF9}" ProjectSection(SolutionItems) = preProject - appveyor.yml = appveyor.yml Build\build.ps1 = Build\build.ps1 Build\MQTTnet.AspNetCore.nuspec = Build\MQTTnet.AspNetCore.nuspec Build\MQTTnet.Extensions.ManagedClient.nuspec = Build\MQTTnet.Extensions.ManagedClient.nuspec From eb48f8766729d42cfdcf909f59d4f332307ff6a7 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Jun 2018 21:50:17 +0200 Subject: [PATCH 092/138] Update packages --- Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj | 6 +++--- .../MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj | 2 +- .../MQTTnet.TestApp.UniversalWindows.csproj | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj index d3ada44d..89e96e2a 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj +++ b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj index cf1aa34e..0f2d0bfb 100644 --- a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj +++ b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj @@ -11,7 +11,7 @@ - + diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj index 3bf7ee80..ae621c22 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj @@ -145,7 +145,7 @@ 6.1.4 - 2.2.0 + 3.0.0 From 840d8ab4fd8399f2111e1a0b806444ef7f8a0ce0 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Jun 2018 22:04:37 +0200 Subject: [PATCH 093/138] Refactor TCP channel. --- .../Implementations/MqttTcpChannel.Uwp.cs | 65 ++++++------------- .../Implementations/MqttTcpChannel.cs | 49 ++++++-------- 2 files changed, 41 insertions(+), 73 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs index a325ebe1..e8406d36 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs @@ -92,50 +92,9 @@ namespace MQTTnet.Implementations public void Dispose() { - try - { - _readStream?.Dispose(); - } - catch (ObjectDisposedException) - { - } - catch (NullReferenceException) - { - } - finally - { - _readStream = null; - } - - try - { - _writeStream?.Dispose(); - } - catch (ObjectDisposedException) - { - } - catch (NullReferenceException) - { - } - finally - { - _writeStream = null; - } - - try - { - _socket?.Dispose(); - } - catch (ObjectDisposedException) - { - } - catch (NullReferenceException) - { - } - finally - { - _socket = null; - } + TryDispose(_readStream, () => _readStream = null); + TryDispose(_writeStream, () => _writeStream = null); + TryDispose(_socket, () => _socket = null); } private static Certificate LoadCertificate(MqttClientTcpOptions options) @@ -187,6 +146,24 @@ namespace MQTTnet.Implementations _readStream = _socket.InputStream.AsStreamForRead(_bufferSize); _writeStream = _socket.OutputStream.AsStreamForWrite(_bufferSize); } + + private static void TryDispose(IDisposable disposable, Action afterDispose) + { + try + { + disposable?.Dispose(); + } + catch (ObjectDisposedException) + { + } + catch (NullReferenceException) + { + } + finally + { + afterDispose(); + } + } } } #endif \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs index ab2675a8..7e4148ff 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs @@ -84,35 +84,8 @@ namespace MQTTnet.Implementations public void Dispose() { - try - { - _stream?.Dispose(); - } - catch (ObjectDisposedException) - { - } - catch (NullReferenceException) - { - } - finally - { - _stream = null; - } - - try - { - _socket?.Dispose(); - } - catch (ObjectDisposedException) - { - } - catch (NullReferenceException) - { - } - finally - { - _socket = null; - } + TryDispose(_stream, () => _stream = null); + TryDispose(_socket, () => _socket = null); } private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) @@ -173,6 +146,24 @@ namespace MQTTnet.Implementations _stream = new NetworkStream(_socket, true); } } + + private static void TryDispose(IDisposable disposable, Action afterDispose) + { + try + { + disposable?.Dispose(); + } + catch (ObjectDisposedException) + { + } + catch (NullReferenceException) + { + } + finally + { + afterDispose(); + } + } } } #endif From 841a22b2b2c387475643b747e0c34489d3046dc2 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Jun 2018 22:08:46 +0200 Subject: [PATCH 094/138] Refactor channel adapter. --- .../Adapter/MqttChannelAdapter.cs | 43 +++++++------------ 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 218fcb93..49c04e84 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -177,34 +177,17 @@ namespace MQTTnet.Adapter { await action().ConfigureAwait(false); } - catch (TaskCanceledException) + catch (Exception exception) { - throw; - } - catch (OperationCanceledException) - { - throw; - } - catch (MqttCommunicationTimedOutException) - { - throw; - } - catch (MqttCommunicationException) - { - throw; - } - catch (COMException comException) - { - if ((uint)comException.HResult == ErrorOperationAborted) + if (exception is TaskCanceledException || + exception is OperationCanceledException || + exception is MqttCommunicationTimedOutException || + exception is MqttCommunicationException) { - throw new OperationCanceledException(); + throw; } - throw new MqttCommunicationException(comException); - } - catch (IOException exception) - { - if (exception.InnerException is SocketException socketException) + if (exception is IOException && exception.InnerException is SocketException socketException) { if (socketException.SocketErrorCode == SocketError.ConnectionAborted) { @@ -212,10 +195,14 @@ namespace MQTTnet.Adapter } } - throw new MqttCommunicationException(exception); - } - catch (Exception exception) - { + if (exception is COMException comException) + { + if ((uint)comException.HResult == ErrorOperationAborted) + { + throw new OperationCanceledException(); + } + } + throw new MqttCommunicationException(exception); } } From 9e318fc40d74bb9fefe5e5ed38564c4bfc42ef25 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Jun 2018 22:10:34 +0200 Subject: [PATCH 095/138] Update BetterCodeHub config. --- .bettercodehub.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.bettercodehub.yml b/.bettercodehub.yml index 73ad89d2..496d2d58 100644 --- a/.bettercodehub.yml +++ b/.bettercodehub.yml @@ -1,5 +1,3 @@ -exclude: -- /Tests/.* component_depth: 2 languages: - csharp From e62316105f9d57dd66000dbddb27ee5065f32b63 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 9 Jun 2018 15:31:47 +0200 Subject: [PATCH 096/138] Refactor server socket management for Linux support. --- Build/MQTTnet.nuspec | 1 + .../MQTTnet.Extensions.ManagedClient.csproj | 2 +- .../MQTTnet.Extensions.Rpc.csproj | 2 +- .../MQTTnet.AspnetCore.csproj | 2 +- .../MqttTcpServerAdapter.Uwp.cs | 4 +- .../Implementations/MqttTcpServerAdapter.cs | 148 ++++++------------ .../Implementations/MqttTcpServerListener.cs | 109 +++++++++++++ ...Tnet.Netstandard.csproj => MQTTnet.csproj} | 0 .../Server/IMqttServerOptions.cs | 6 +- .../MqttServerDefaultEndpointOptions.cs | 13 -- .../Server/MqttServerOptions.cs | 6 +- .../Server/MqttServerOptionsBuilder.cs | 19 ++- .../Server/MqttServerOptionsExtensions.cs | 31 ---- .../MqttServerTcpEndpointBaseOptions.cs | 17 ++ .../Server/MqttServerTcpEndpointOptions.cs | 11 ++ .../Server/MqttServerTlsEndpointOptions.cs | 15 -- .../Server/MqttServerTlsTcpEndpointOptions.cs | 12 ++ MQTTnet.sln | 2 +- .../MQTTnet.Benchmarks.csproj | 2 +- Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs | 3 +- .../MQTTnet.Core.Tests.csproj | 2 +- .../MQTTnet.TestApp.NetCore.csproj | 2 +- .../PerformanceTest.cs | 1 + .../MQTTnet.TestApp.UniversalWindows.csproj | 4 +- 24 files changed, 229 insertions(+), 185 deletions(-) create mode 100644 Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerListener.cs rename Frameworks/MQTTnet.NetStandard/{MQTTnet.Netstandard.csproj => MQTTnet.csproj} (100%) delete mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttServerDefaultEndpointOptions.cs delete mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsExtensions.cs create mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointBaseOptions.cs create mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointOptions.cs delete mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsEndpointOptions.cs create mode 100644 Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsTcpEndpointOptions.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 71ec12d4..8d130231 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -28,6 +28,7 @@ * [Server] Rewritten the _ConnectedClients_ API and added new features for disconnecting and Endpoint information (IP etc.). * [Server] Added settings for disabling persistent sessions and defining a max pending messages queue size per session. * [Server] Added a new interceptor which is invoked before a new message is added to the client queue. +* [Server] Added support for Linux servers by dividing IPv4 and IPv6 support and adding new options (BREAKING CHANGE!). Copyright Christian Kratky 2016-2018 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 diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj b/Extensions/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj index 64bbf92c..46e9a0af 100644 --- a/Extensions/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj +++ b/Extensions/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj @@ -25,7 +25,7 @@ - + diff --git a/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj b/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj index 64bbf92c..46e9a0af 100644 --- a/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj +++ b/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj @@ -25,7 +25,7 @@ - + diff --git a/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj b/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj index d3f06e82..1bc391b9 100644 --- a/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj +++ b/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj @@ -22,7 +22,7 @@ - + diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs index 26bae2e1..26759b2e 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs @@ -38,8 +38,8 @@ namespace MQTTnet.Implementations _defaultEndpointSocket.Control.KeepAlive = true; _defaultEndpointSocket.Control.QualityOfService = SocketQualityOfService.LowLatency; _defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync; - - await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket); + + await _defaultEndpointSocket.BindServiceNameAsync(options.DefaultEndpointOptions.Port.ToString(), SocketProtectionLevel.PlainSocket); } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs index 40f6b2ef..38869bdb 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs @@ -1,28 +1,23 @@ #if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0 using System; -using System.Net; -using System.Net.Security; +using System.Collections.Generic; using System.Net.Sockets; -using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; -using MQTTnet.Serializer; using MQTTnet.Server; namespace MQTTnet.Implementations { public class MqttTcpServerAdapter : IMqttServerAdapter { + private readonly List _listeners = new List(); private readonly IMqttNetChildLogger _logger; - + private CancellationTokenSource _cancellationTokenSource; - private Socket _defaultEndpointSocket; - private Socket _tlsEndpointSocket; - private X509Certificate2 _tlsCertificate; - + public MqttTcpServerAdapter(IMqttNetChildLogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); @@ -40,13 +35,7 @@ namespace MQTTnet.Implementations if (options.DefaultEndpointOptions.IsEnabled) { - _defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; - - _defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort())); - _defaultEndpointSocket.Listen(options.ConnectionBacklog); - - Task.Run(() => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token), - _cancellationTokenSource.Token); + RegisterListeners(options.DefaultEndpointOptions); } if (options.TlsEndpointOptions.IsEnabled) @@ -56,19 +45,13 @@ namespace MQTTnet.Implementations throw new ArgumentException("TLS certificate is not set."); } - _tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate); - if (!_tlsCertificate.HasPrivateKey) + var tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate); + if (!tlsCertificate.HasPrivateKey) { throw new InvalidOperationException("The certificate for TLS encryption must contain the private key."); } - _tlsEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); - _tlsEndpointSocket.Bind(new IPEndPoint(options.TlsEndpointOptions.BoundIPAddress, options.GetTlsEndpointPort())); - _tlsEndpointSocket.Listen(options.ConnectionBacklog); - - Task.Run( - () => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), - _cancellationTokenSource.Token); + RegisterListeners(options.TlsEndpointOptions); } return Task.FromResult(0); @@ -76,93 +59,60 @@ namespace MQTTnet.Implementations public Task StopAsync() { - _cancellationTokenSource?.Cancel(false); - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; - - _defaultEndpointSocket?.Dispose(); - _defaultEndpointSocket = null; - - _tlsCertificate = null; - - _tlsEndpointSocket?.Dispose(); - _tlsEndpointSocket = null; - + Dispose(); return Task.FromResult(0); } public void Dispose() { - StopAsync().GetAwaiter().GetResult(); + _cancellationTokenSource?.Cancel(false); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + + foreach (var listener in _listeners) + { + listener.Dispose(); + } + + _listeners.Clear(); } - private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken) + private void RegisterListeners(MqttServerTcpEndpointBaseOptions options) { - while (!cancellationToken.IsCancellationRequested) + var tlsOptions = options as MqttServerTlsTcpEndpointOptions; + + X509Certificate2 tlsCertificate = null; + if (tlsOptions != null) { - try - { - //todo: else branch can be used with min dependency NET46 -#if NET452 || NET461 - var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false); -#else - var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false); -#endif - clientSocket.NoDelay = true; - - var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger); - ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); - } - catch (ObjectDisposedException) - { - // It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. - } - catch (Exception exception) - { - if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted) - { - return; - } - - _logger.Error(exception, "Error while accepting connection at default endpoint."); - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); - } + tlsCertificate = new X509Certificate2(tlsOptions.Certificate); } + + var listenerV4 = new MqttTcpServerListener( + AddressFamily.InterNetwork, + options, + tlsCertificate, + _cancellationTokenSource.Token, + _logger); + + listenerV4.ClientAccepted += OnClientAccepted; + listenerV4.Start(); + _listeners.Add(listenerV4); + + var listenerV6 = new MqttTcpServerListener( + AddressFamily.InterNetworkV6, + options, + tlsCertificate, + _cancellationTokenSource.Token, + _logger); + + listenerV6.ClientAccepted += OnClientAccepted; + listenerV6.Start(); + _listeners.Add(listenerV6); } - private async Task AcceptTlsEndpointConnectionsAsync(CancellationToken cancellationToken) + private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs e) { - while (!cancellationToken.IsCancellationRequested) - { - try - { -#if NET452 || NET461 - var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null).ConfigureAwait(false); -#else - var clientSocket = await _tlsEndpointSocket.AcceptAsync().ConfigureAwait(false); -#endif - - var sslStream = new SslStream(new NetworkStream(clientSocket)); - await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); - - var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger); - ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); - } - catch (ObjectDisposedException) - { - // It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. - } - catch (Exception exception) - { - if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted) - { - return; - } - - _logger.Error(exception, "Error while accepting connection at TLS endpoint."); - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); - } - } + ClientAccepted?.Invoke(this, e); } } } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerListener.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerListener.cs new file mode 100644 index 00000000..4bf2b081 --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerListener.cs @@ -0,0 +1,109 @@ +#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0 +using System; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Adapter; +using MQTTnet.Diagnostics; +using MQTTnet.Serializer; +using MQTTnet.Server; + +namespace MQTTnet.Implementations +{ + public class MqttTcpServerListener : IDisposable + { + private readonly IMqttNetChildLogger _logger; + private readonly CancellationToken _cancellationToken; + private readonly AddressFamily _addressFamily; + private readonly MqttServerTcpEndpointBaseOptions _options; + private readonly X509Certificate2 _tlsCertificate; + + private Socket _socket; + + public MqttTcpServerListener( + AddressFamily addressFamily, + MqttServerTcpEndpointBaseOptions options, + X509Certificate2 tlsCertificate, + CancellationToken cancellationToken, + IMqttNetChildLogger logger) + { + _addressFamily = addressFamily; + _options = options; + _tlsCertificate = tlsCertificate; + _cancellationToken = cancellationToken; + _logger = logger.CreateChildLogger(nameof(MqttTcpServerListener)); + } + + public event EventHandler ClientAccepted; + + public void Start() + { + var boundIp = _options.BoundInterNetworkAddress; + if (_addressFamily == AddressFamily.InterNetworkV6) + { + boundIp = _options.BoundInterNetworkV6Address; + } + + _socket = new Socket(_addressFamily, SocketType.Stream, ProtocolType.Tcp); + _socket.Bind(new IPEndPoint(boundIp, _options.Port)); + + _logger.Info($"Starting TCP listener for {_socket.LocalEndPoint} TLS={_tlsCertificate != null}."); + + _socket.Listen(_options.ConnectionBacklog); + Task.Run(AcceptClientConnectionsAsync, _cancellationToken); + } + + private async Task AcceptClientConnectionsAsync() + { + while (!_cancellationToken.IsCancellationRequested) + { + try + { +#if NET452 || NET461 + var clientSocket = await Task.Factory.FromAsync(_socket.BeginAccept, _socket.EndAccept, null).ConfigureAwait(false); +#else + var clientSocket = await _socket.AcceptAsync().ConfigureAwait(false); +#endif + clientSocket.NoDelay = true; + + if (_tlsCertificate != null) + { + var sslStream = new SslStream(new NetworkStream(clientSocket), false); + await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); + } + + var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger); + ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); + } + catch (ObjectDisposedException) + { + // It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. + } + catch (Exception exception) + { + if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted) + { + return; + } + + _logger.Error(exception, $"Error while accepting connection at TCP listener {_socket.LocalEndPoint} TLS={_tlsCertificate != null}."); + await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken).ConfigureAwait(false); + } + } + } + + public void Dispose() + { + _socket?.Dispose(); + +#if NETSTANDARD1_3 || NETSTANDARD2_0 || NET461 + _tlsCertificate?.Dispose(); +#endif + } + } +} +#endif \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj b/Frameworks/MQTTnet.NetStandard/MQTTnet.csproj similarity index 100% rename from Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj rename to Frameworks/MQTTnet.NetStandard/MQTTnet.csproj diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs index b753587d..2ae4c9fa 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs @@ -4,8 +4,6 @@ namespace MQTTnet.Server { public interface IMqttServerOptions { - int ConnectionBacklog { get; } - bool EnablePersistentSessions { get; } int MaxPendingMessagesPerClient { get; } @@ -18,8 +16,8 @@ namespace MQTTnet.Server Action ApplicationMessageInterceptor { get; } Action ClientMessageQueueInterceptor { get; set; } - MqttServerDefaultEndpointOptions DefaultEndpointOptions { get; } - MqttServerTlsEndpointOptions TlsEndpointOptions { get; } + MqttServerTcpEndpointOptions DefaultEndpointOptions { get; } + MqttServerTlsTcpEndpointOptions TlsEndpointOptions { get; } IMqttServerStorage Storage { get; } } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerDefaultEndpointOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerDefaultEndpointOptions.cs deleted file mode 100644 index 0965104b..00000000 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerDefaultEndpointOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Net; - -namespace MQTTnet.Server -{ - public class MqttServerDefaultEndpointOptions - { - public bool IsEnabled { get; set; } = true; - - public int? Port { get; set; } - - public IPAddress BoundIPAddress { get; set; } = IPAddress.Any; - } -} diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs index 4d06b942..6c7c4f94 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs @@ -4,14 +4,12 @@ namespace MQTTnet.Server { public class MqttServerOptions : IMqttServerOptions { - public MqttServerDefaultEndpointOptions DefaultEndpointOptions { get; } = new MqttServerDefaultEndpointOptions(); + public MqttServerTcpEndpointOptions DefaultEndpointOptions { get; } = new MqttServerTcpEndpointOptions(); - public MqttServerTlsEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsEndpointOptions(); + public MqttServerTlsTcpEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsTcpEndpointOptions(); public bool EnablePersistentSessions { get; set; } - public int ConnectionBacklog { get; set; } = 10; - public int MaxPendingMessagesPerClient { get; set; } = 250; public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage; diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs index a8800d09..f54f379f 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs @@ -9,7 +9,8 @@ namespace MQTTnet.Server public MqttServerOptionsBuilder WithConnectionBacklog(int value) { - _options.ConnectionBacklog = value; + _options.DefaultEndpointOptions.ConnectionBacklog = value; + _options.TlsEndpointOptions.ConnectionBacklog = value; return this; } @@ -31,7 +32,7 @@ namespace MQTTnet.Server return this; } - public MqttServerOptionsBuilder WithDefaultEndpointPort(int? value) + public MqttServerOptionsBuilder WithDefaultEndpointPort(int value) { _options.DefaultEndpointOptions.Port = value; return this; @@ -39,10 +40,16 @@ namespace MQTTnet.Server public MqttServerOptionsBuilder WithDefaultEndpointBoundIPAddress(IPAddress value) { - _options.DefaultEndpointOptions.BoundIPAddress = value ?? IPAddress.Any; + _options.DefaultEndpointOptions.BoundInterNetworkAddress = value ?? IPAddress.Any; return this; } - + + public MqttServerOptionsBuilder WithDefaultEndpointBoundIPV6Address(IPAddress value) + { + _options.DefaultEndpointOptions.BoundInterNetworkV6Address = value ?? IPAddress.Any; + return this; + } + public MqttServerOptionsBuilder WithoutDefaultEndpoint() { _options.DefaultEndpointOptions.IsEnabled = false; @@ -55,7 +62,7 @@ namespace MQTTnet.Server return this; } - public MqttServerOptionsBuilder WithEncryptedEndpointPort(int? value) + public MqttServerOptionsBuilder WithEncryptedEndpointPort(int value) { _options.TlsEndpointOptions.Port = value; return this; @@ -63,7 +70,7 @@ namespace MQTTnet.Server public MqttServerOptionsBuilder WithEncryptedEndpointBoundIPAddress(IPAddress value) { - _options.TlsEndpointOptions.BoundIPAddress = value; + _options.TlsEndpointOptions.BoundInterNetworkAddress = value; return this; } diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsExtensions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsExtensions.cs deleted file mode 100644 index db93ef18..00000000 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace MQTTnet.Server -{ - public static class MqttServerOptionsExtensions - { - public static int GetTlsEndpointPort(this IMqttServerOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - - if (!options.TlsEndpointOptions.Port.HasValue) - { - return 8883; - } - - return options.TlsEndpointOptions.Port.Value; - } - - public static int GetDefaultEndpointPort(this IMqttServerOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - - if (!options.DefaultEndpointOptions.Port.HasValue) - { - return 1883; - } - - return options.DefaultEndpointOptions.Port.Value; - } - } -} diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointBaseOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointBaseOptions.cs new file mode 100644 index 00000000..8ef8d41b --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointBaseOptions.cs @@ -0,0 +1,17 @@ +using System.Net; + +namespace MQTTnet.Server +{ + public abstract class MqttServerTcpEndpointBaseOptions + { + public bool IsEnabled { get; set; } + + public int Port { get; set; } + + public int ConnectionBacklog { get; set; } = 10; + + public IPAddress BoundInterNetworkAddress { get; set; } = IPAddress.Any; + + public IPAddress BoundInterNetworkV6Address { get; set; } = IPAddress.IPv6Any; + } +} \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointOptions.cs new file mode 100644 index 00000000..0eb6dc2b --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointOptions.cs @@ -0,0 +1,11 @@ +namespace MQTTnet.Server +{ + public class MqttServerTcpEndpointOptions : MqttServerTcpEndpointBaseOptions + { + public MqttServerTcpEndpointOptions() + { + IsEnabled = true; + Port = 1883; + } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsEndpointOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsEndpointOptions.cs deleted file mode 100644 index aaf4948b..00000000 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsEndpointOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Net; - -namespace MQTTnet.Server -{ - public class MqttServerTlsEndpointOptions - { - public bool IsEnabled { get; set; } - - public int? Port { get; set; } - - public byte[] Certificate { get; set; } - - public IPAddress BoundIPAddress { get; set; } = IPAddress.Any; - } -} diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsTcpEndpointOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsTcpEndpointOptions.cs new file mode 100644 index 00000000..76d6c862 --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsTcpEndpointOptions.cs @@ -0,0 +1,12 @@ +namespace MQTTnet.Server +{ + public class MqttServerTlsTcpEndpointOptions : MqttServerTcpEndpointBaseOptions + { + public MqttServerTlsTcpEndpointOptions() + { + Port = 8883; + } + + public byte[] Certificate { get; set; } + } +} diff --git a/MQTTnet.sln b/MQTTnet.sln index 2e487b07..68a35f12 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -11,7 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp.UniversalWindows", "Tests\MQTTnet.TestApp.UniversalWindows\MQTTnet.TestApp.UniversalWindows.csproj", "{FF1F72D6-9524-4422-9497-3CC0002216ED}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.NetStandard", "Frameworks\MQTTnet.NetStandard\MQTTnet.NetStandard.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Frameworks\MQTTnet.NetStandard\MQTTnet.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1-BC3A-420A-BE9C-FA2401431CF9}" ProjectSection(SolutionItems) = preProject diff --git a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index ad3aa55a..4db064ff 100644 --- a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs b/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs index 96b1d858..83cb98a8 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs +++ b/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.IO.Pipelines; using System.Net; @@ -21,7 +20,7 @@ namespace MQTTnet.Benchmarks.Tcp public TcpConnection(EndPoint endPoint) { - _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _endPoint = endPoint; _sender = new SocketSender(_socket, PipeScheduler.ThreadPool); diff --git a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj index 89e96e2a..52e3326c 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj +++ b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj index 0f2d0bfb..50501637 100644 --- a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj +++ b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj @@ -16,7 +16,7 @@ - + diff --git a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index 27d8077b..e9e4bc46 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -18,6 +18,7 @@ namespace MQTTnet.TestApp.NetCore var concurrent = Console.ReadKey(true).KeyChar == 'c'; var server = Task.Run(RunServerAsync); + await Task.Delay(1000); var client = Task.Run(() => RunClientAsync(2000, TimeSpan.FromMilliseconds(10), concurrent)); await Task.WhenAll(server, client).ConfigureAwait(false); diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj index ae621c22..0b6cad83 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj @@ -135,9 +135,9 @@ {c444e9c8-95fa-430e-9126-274129de16cd} MQTTnet.Extensions.Rpc - + {3587e506-55a2-4eb3-99c7-dc01e42d25d2} - MQTTnet.NetStandard + MQTTnet From 6bcfa436ea3541bce77908ceb5b4700deda7f9b4 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 9 Jun 2018 21:57:04 +0200 Subject: [PATCH 097/138] Fix TLS certificate loading. --- .../Adapter/MqttChannelAdapter.cs | 5 ----- .../Implementations/MqttTcpServerAdapter.cs | 20 ++++++------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs index 49c04e84..605997a3 100644 --- a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs @@ -65,11 +65,6 @@ namespace MQTTnet.Adapter foreach (var packet in packets) { - if (packet == null) - { - continue; - } - await SendPacketAsync(timeout, cancellationToken, packet).ConfigureAwait(false); } } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs index 38869bdb..8783e9cf 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs @@ -15,9 +15,9 @@ namespace MQTTnet.Implementations { private readonly List _listeners = new List(); private readonly IMqttNetChildLogger _logger; - + private CancellationTokenSource _cancellationTokenSource; - + public MqttTcpServerAdapter(IMqttNetChildLogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); @@ -35,7 +35,7 @@ namespace MQTTnet.Implementations if (options.DefaultEndpointOptions.IsEnabled) { - RegisterListeners(options.DefaultEndpointOptions); + RegisterListeners(options.DefaultEndpointOptions, null); } if (options.TlsEndpointOptions.IsEnabled) @@ -51,7 +51,7 @@ namespace MQTTnet.Implementations throw new InvalidOperationException("The certificate for TLS encryption must contain the private key."); } - RegisterListeners(options.TlsEndpointOptions); + RegisterListeners(options.TlsEndpointOptions, tlsCertificate); } return Task.FromResult(0); @@ -77,16 +77,8 @@ namespace MQTTnet.Implementations _listeners.Clear(); } - private void RegisterListeners(MqttServerTcpEndpointBaseOptions options) + private void RegisterListeners(MqttServerTcpEndpointBaseOptions options, X509Certificate2 tlsCertificate) { - var tlsOptions = options as MqttServerTlsTcpEndpointOptions; - - X509Certificate2 tlsCertificate = null; - if (tlsOptions != null) - { - tlsCertificate = new X509Certificate2(tlsOptions.Certificate); - } - var listenerV4 = new MqttTcpServerListener( AddressFamily.InterNetwork, options, @@ -97,7 +89,7 @@ namespace MQTTnet.Implementations listenerV4.ClientAccepted += OnClientAccepted; listenerV4.Start(); _listeners.Add(listenerV4); - + var listenerV6 = new MqttTcpServerListener( AddressFamily.InterNetworkV6, options, From 20779dfece00a65b15585ed0e13bd22910d34585 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Mon, 11 Jun 2018 00:00:30 +0200 Subject: [PATCH 098/138] Reorganize solution structure. --- Build/MQTTnet.AspNetCore.nuspec | 2 +- Build/MQTTnet.Extensions.ManagedClient.nuspec | 10 +++--- Build/MQTTnet.Extensions.Rpc.nuspec | 10 +++--- Build/MQTTnet.nuspec | 10 +++--- Build/build.ps1 | 30 ++++++++--------- MQTTnet.sln | 10 +++--- .../ApplicationBuilderExtensions.cs | 0 .../MQTTnet.AspNetCore.csproj | 4 +-- .../MQTTnet.AspnetCore/MqttHostedServer.cs | 0 .../MqttWebSocketServerAdapter.cs | 0 .../ServiceCollectionExtensions.cs | 0 .../ApplicationMessageProcessedEventArgs.cs | 0 .../IManagedMqttClient.cs | 0 .../IManagedMqttClientOptions.cs | 0 .../IManagedMqttClientStorage.cs | 0 .../MQTTnet.Extensions.ManagedClient.csproj | 2 +- .../ManagedMqttApplicationMessage.cs | 0 .../ManagedMqttApplicationMessageBuilder.cs | 0 .../ManagedMqttClient.cs | 0 .../ManagedMqttClientExtensions.cs | 0 .../ManagedMqttClientOptions.cs | 0 .../ManagedMqttClientOptionsBuilder.cs | 0 .../ManagedMqttClientStorageManager.cs | 0 .../MqttFactoryExtensions.cs | 0 .../ReconnectionResult.cs | 0 .../MQTTnet.Extensions.Rpc.csproj | 2 +- .../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 0 .../MQTTnet.Extensions.Rpc/SampleCCode.c | 0 .../MQTTnet}/Adapter/IMqttChannelAdapter.cs | 0 .../MQTTnet}/Adapter/IMqttServerAdapter.cs | 0 .../MQTTnet}/Adapter/MqttChannelAdapter.cs | 0 .../Adapter/MqttConnectingFailedException.cs | 0 ...qttServerAdapterClientAcceptedEventArgs.cs | 0 .../MQTTnet}/Adapter/ReceivedMqttPacket.cs | 0 .../ApplicationMessagePublisherExtensions.cs | 0 .../MQTTnet}/Channel/IMqttChannel.cs | 0 .../MQTTnet}/Client/IMqttClient.cs | 0 .../Client/IMqttClientAdapterFactory.cs | 0 .../Client/IMqttClientChannelOptions.cs | 0 .../MQTTnet}/Client/IMqttClientCredentials.cs | 0 .../MQTTnet}/Client/IMqttClientFactory.cs | 0 .../MQTTnet}/Client/IMqttClientOptions.cs | 0 .../MQTTnet}/Client/MqttClient.cs | 0 .../Client/MqttClientConnectResult.cs | 0 .../Client/MqttClientConnectedEventArgs.cs | 0 .../MQTTnet}/Client/MqttClientCredentials.cs | 0 .../Client/MqttClientDisconnectedEventArgs.cs | 0 .../MQTTnet}/Client/MqttClientExtensions.cs | 0 .../MQTTnet}/Client/MqttClientOptions.cs | 0 .../Client/MqttClientOptionsBuilder.cs | 0 .../MQTTnet}/Client/MqttClientTcpOptions.cs | 0 .../Client/MqttClientTcpOptionsExtensions.cs | 0 .../MQTTnet}/Client/MqttClientTlsOptions.cs | 7 +++- .../Client/MqttClientWebSocketOptions.cs | 0 .../MQTTnet}/Client/MqttPacketDispatcher.cs | 0 .../Client/MqttPacketIdentifierProvider.cs | 0 .../MQTTnet}/Client/MqttSubscribeResult.cs | 0 .../Diagnostics/IMqttNetChildLogger.cs | 0 .../MQTTnet}/Diagnostics/IMqttNetLogger.cs | 0 .../Diagnostics/MqttNetChildLogger.cs | 0 .../Diagnostics/MqttNetGlobalLogger.cs | 0 .../MQTTnet}/Diagnostics/MqttNetLogLevel.cs | 0 .../MQTTnet}/Diagnostics/MqttNetLogMessage.cs | 0 .../MqttNetLogMessagePublishedEventArgs.cs | 0 .../MQTTnet}/Diagnostics/MqttNetLogger.cs | 0 .../TargetFrameworkInfoProvider.cs | 0 .../Exceptions/MqttCommunicationException.cs | 0 .../MqttCommunicationTimedOutException.cs | 0 .../MqttProtocolViolationException.cs | 0 .../MQTTnet}/IApplicationMessagePublisher.cs | 0 .../MQTTnet}/IApplicationMessageReceiver.cs | 0 .../MqttClientAdapterFactory.cs | 4 +-- .../Implementations/MqttTcpChannel.Uwp.cs | 19 +++++------ .../Implementations/MqttTcpChannel.cs | 16 +++++++-- .../MqttTcpServerAdapter.Uwp.cs | 31 +++++++++--------- .../Implementations/MqttTcpServerAdapter.cs | 0 .../Implementations/MqttTcpServerListener.cs | 0 .../Implementations/MqttWebSocketChannel.cs | 0 .../MQTTnet}/Internal/AsyncAutoResetEvent.cs | 0 .../MQTTnet}/Internal/AsyncLock.cs | 0 .../MQTTnet}/Internal/ExceptionHelper.cs | 0 .../MqttApplicationMessageExtensions.cs | 0 .../MQTTnet}/Internal/TaskExtensions.cs | 0 .../MQTTnet}/Internal/TestMqttChannel.cs | 0 .../MQTTnet}/MQTTnet.csproj | 0 .../MQTTnet}/MqttApplicationMessage.cs | 0 .../MQTTnet}/MqttApplicationMessageBuilder.cs | 0 .../MqttApplicationMessageExtensions.cs | 0 ...MqttApplicationMessageReceivedEventArgs.cs | 0 .../MQTTnet}/MqttFactory.cs | 0 .../Packets/IMqttPacketWithIdentifier.cs | 0 .../MQTTnet}/Packets/MqttBasePacket.cs | 0 .../MQTTnet}/Packets/MqttBasePublishPacket.cs | 0 .../MQTTnet}/Packets/MqttConnAckPacket.cs | 0 .../MQTTnet}/Packets/MqttConnectPacket.cs | 0 .../MQTTnet}/Packets/MqttDisconnectPacket.cs | 0 .../MQTTnet}/Packets/MqttPingReqPacket.cs | 0 .../MQTTnet}/Packets/MqttPingRespPacket.cs | 0 .../MQTTnet}/Packets/MqttPubAckPacket.cs | 0 .../MQTTnet}/Packets/MqttPubCompPacket.cs | 0 .../MQTTnet}/Packets/MqttPubRecPacket.cs | 0 .../MQTTnet}/Packets/MqttPubRelPacket.cs | 0 .../MQTTnet}/Packets/MqttPublishPacket.cs | 0 .../MQTTnet}/Packets/MqttSubAckPacket.cs | 0 .../MQTTnet}/Packets/MqttSubscribePacket.cs | 0 .../MQTTnet}/Packets/MqttUnsubAckPacket.cs | 0 .../MQTTnet}/Packets/MqttUnsubscribe.cs | 0 .../Protocol/MqttConnectReturnCode.cs | 0 .../Protocol/MqttControlPacketType.cs | 0 .../Protocol/MqttQualityOfServiceLevel.cs | 0 .../Protocol/MqttSubscribeReturnCode.cs | 0 .../MQTTnet}/Serializer/ByteReader.cs | 0 .../MQTTnet}/Serializer/ByteWriter.cs | 0 .../MQTTnet}/Serializer/Extensions.cs | 0 .../Serializer/IMqttPacketSerializer.cs | 0 .../MQTTnet}/Serializer/MqttFixedHeader.cs | 0 .../Serializer/MqttPacketBodyReader.cs | 0 .../MQTTnet}/Serializer/MqttPacketReader.cs | 0 .../Serializer/MqttPacketSerializer.cs | 0 .../MQTTnet}/Serializer/MqttPacketWriter.cs | 0 .../Serializer/MqttProtocolVersion.cs | 0 .../Server/CheckSubscriptionsResult.cs | 0 .../Server/GetOrCreateClientSessionResult.cs | 0 .../Server/IMqttClientSessionStatus.cs | 0 .../MQTTnet}/Server/IMqttServer.cs | 0 .../MQTTnet}/Server/IMqttServerFactory.cs | 0 .../MQTTnet}/Server/IMqttServerOptions.cs | 0 .../MQTTnet}/Server/IMqttServerStorage.cs | 0 ...qttApplicationMessageInterceptorContext.cs | 0 .../Server/MqttClientConnectedEventArgs.cs | 0 .../Server/MqttClientDisconnectType.cs | 0 .../Server/MqttClientDisconnectedEventArgs.cs | 0 .../Server/MqttClientKeepAliveMonitor.cs | 0 ...qttClientMessageQueueInterceptorContext.cs | 0 .../Server/MqttClientPendingMessagesQueue.cs | 0 .../MQTTnet}/Server/MqttClientSession.cs | 0 .../Server/MqttClientSessionStatus.cs | 0 .../Server/MqttClientSessionsManager.cs | 0 .../Server/MqttClientSubscribeResult.cs | 0 .../MqttClientSubscribedTopicEventArgs.cs | 0 .../Server/MqttClientSubscriptionsManager.cs | 0 .../MqttClientUnSubscribedTopicEventArgs.cs | 0 .../Server/MqttConnectionValidatorContext.cs | 0 .../MqttPendingMessagesOverflowStrategy.cs | 0 .../Server/MqttRetainedMessagesManager.cs | 0 .../MQTTnet}/Server/MqttServer.cs | 0 .../MQTTnet}/Server/MqttServerOptions.cs | 0 .../Server/MqttServerOptionsBuilder.cs | 0 .../MqttServerTcpEndpointBaseOptions.cs | 4 +++ .../Server/MqttServerTcpEndpointOptions.cs | 0 .../Server/MqttServerTlsTcpEndpointOptions.cs | 0 .../MQTTnet}/Server/MqttSubscribeResult.cs | 0 .../MqttSubscriptionInterceptorContext.cs | 0 .../Server/MqttTopicFilterComparer.cs | 0 .../MQTTnet}/TopicFilter.cs | 0 .../MQTTnet}/TopicFilterBuilder.cs | 0 .../MQTTnet}/codeSigningKey.pfx | Bin .../MQTTnet.Benchmarks.csproj | 2 +- .../MqttTcpChannelBenchmark.cs | 10 +++--- .../MQTTnet.Core.Tests.csproj | 2 +- .../MQTTnet.TestApp.AspNetCore2.csproj | 5 +-- .../MQTTnet.TestApp.NetCore.csproj | 6 ++-- .../MQTTnet.TestApp.UniversalWindows.csproj | 6 ++-- 163 files changed, 104 insertions(+), 88 deletions(-) rename {Frameworks => Source}/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs (100%) rename Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj => Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj (87%) rename {Frameworks => Source}/MQTTnet.AspnetCore/MqttHostedServer.cs (100%) rename {Frameworks => Source}/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs (100%) rename {Frameworks => Source}/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj (93%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessage.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessageBuilder.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj (93%) rename {Extensions => Source}/MQTTnet.Extensions.Rpc/MqttRpcClient.cs (100%) rename {Extensions => Source}/MQTTnet.Extensions.Rpc/SampleCCode.c (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Adapter/IMqttChannelAdapter.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Adapter/IMqttServerAdapter.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Adapter/MqttChannelAdapter.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Adapter/MqttConnectingFailedException.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Adapter/ReceivedMqttPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/ApplicationMessagePublisherExtensions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Channel/IMqttChannel.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/IMqttClient.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/IMqttClientAdapterFactory.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/IMqttClientChannelOptions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/IMqttClientCredentials.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/IMqttClientFactory.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/IMqttClientOptions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClient.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClientConnectResult.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClientConnectedEventArgs.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClientCredentials.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClientDisconnectedEventArgs.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClientExtensions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClientOptions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClientOptionsBuilder.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClientTcpOptions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClientTcpOptionsExtensions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClientTlsOptions.cs (58%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttClientWebSocketOptions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttPacketDispatcher.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttPacketIdentifierProvider.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Client/MqttSubscribeResult.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Diagnostics/IMqttNetChildLogger.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Diagnostics/IMqttNetLogger.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Diagnostics/MqttNetChildLogger.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Diagnostics/MqttNetGlobalLogger.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Diagnostics/MqttNetLogLevel.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Diagnostics/MqttNetLogMessage.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Diagnostics/MqttNetLogger.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Diagnostics/TargetFrameworkInfoProvider.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Exceptions/MqttCommunicationException.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Exceptions/MqttCommunicationTimedOutException.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Exceptions/MqttProtocolViolationException.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/IApplicationMessagePublisher.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/IApplicationMessageReceiver.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Implementations/MqttClientAdapterFactory.cs (91%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Implementations/MqttTcpChannel.Uwp.cs (89%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Implementations/MqttTcpChannel.cs (88%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Implementations/MqttTcpServerAdapter.Uwp.cs (62%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Implementations/MqttTcpServerAdapter.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Implementations/MqttTcpServerListener.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Implementations/MqttWebSocketChannel.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Internal/AsyncAutoResetEvent.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Internal/AsyncLock.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Internal/ExceptionHelper.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Internal/MqttApplicationMessageExtensions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Internal/TaskExtensions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Internal/TestMqttChannel.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/MQTTnet.csproj (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/MqttApplicationMessage.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/MqttApplicationMessageBuilder.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/MqttApplicationMessageExtensions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/MqttApplicationMessageReceivedEventArgs.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/MqttFactory.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/IMqttPacketWithIdentifier.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttBasePacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttBasePublishPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttConnAckPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttConnectPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttDisconnectPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttPingReqPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttPingRespPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttPubAckPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttPubCompPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttPubRecPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttPubRelPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttPublishPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttSubAckPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttSubscribePacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttUnsubAckPacket.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Packets/MqttUnsubscribe.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Protocol/MqttConnectReturnCode.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Protocol/MqttControlPacketType.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Protocol/MqttQualityOfServiceLevel.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Protocol/MqttSubscribeReturnCode.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Serializer/ByteReader.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Serializer/ByteWriter.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Serializer/Extensions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Serializer/IMqttPacketSerializer.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Serializer/MqttFixedHeader.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Serializer/MqttPacketBodyReader.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Serializer/MqttPacketReader.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Serializer/MqttPacketSerializer.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Serializer/MqttPacketWriter.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Serializer/MqttProtocolVersion.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/CheckSubscriptionsResult.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/GetOrCreateClientSessionResult.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/IMqttClientSessionStatus.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/IMqttServer.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/IMqttServerFactory.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/IMqttServerOptions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/IMqttServerStorage.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttApplicationMessageInterceptorContext.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientConnectedEventArgs.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientDisconnectType.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientDisconnectedEventArgs.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientKeepAliveMonitor.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientMessageQueueInterceptorContext.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientPendingMessagesQueue.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientSession.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientSessionStatus.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientSessionsManager.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientSubscribeResult.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientSubscribedTopicEventArgs.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientSubscriptionsManager.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttClientUnSubscribedTopicEventArgs.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttConnectionValidatorContext.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttPendingMessagesOverflowStrategy.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttRetainedMessagesManager.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttServer.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttServerOptions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttServerOptionsBuilder.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttServerTcpEndpointBaseOptions.cs (84%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttServerTcpEndpointOptions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttServerTlsTcpEndpointOptions.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttSubscribeResult.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttSubscriptionInterceptorContext.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/Server/MqttTopicFilterComparer.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/TopicFilter.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/TopicFilterBuilder.cs (100%) rename {Frameworks/MQTTnet.NetStandard => Source/MQTTnet}/codeSigningKey.pfx (100%) diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec index 30abe1cd..9f595626 100644 --- a/Build/MQTTnet.AspNetCore.nuspec +++ b/Build/MQTTnet.AspNetCore.nuspec @@ -23,6 +23,6 @@ - + \ No newline at end of file diff --git a/Build/MQTTnet.Extensions.ManagedClient.nuspec b/Build/MQTTnet.Extensions.ManagedClient.nuspec index 10835bf7..e6815541 100644 --- a/Build/MQTTnet.Extensions.ManagedClient.nuspec +++ b/Build/MQTTnet.Extensions.ManagedClient.nuspec @@ -38,16 +38,16 @@ - + - + - + - - + + \ No newline at end of file diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec index f1e2d3a0..f8667c44 100644 --- a/Build/MQTTnet.Extensions.Rpc.nuspec +++ b/Build/MQTTnet.Extensions.Rpc.nuspec @@ -38,16 +38,16 @@ - + - + - + - - + + \ No newline at end of file diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 8d130231..96389fa2 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -57,16 +57,16 @@ - + - + - + - - + + \ No newline at end of file diff --git a/Build/build.ps1 b/Build/build.ps1 index 1009db00..59f3112c 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -10,28 +10,28 @@ if ($path) { $msbuild = join-path $path 'MSBuild\15.0\Bin\MSBuild.exe' # Build the core library - &$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Build the ASP.NET Core 2.0 extension &$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Build the RPC extension - &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Build the Managed Client extension - &$msbuild ..\Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" - &$msbuild ..\Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue diff --git a/MQTTnet.sln b/MQTTnet.sln index 68a35f12..0e0e60c3 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -7,11 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9248C2E1-B9D6-40BF-81EC-86004D7765B4}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", "{32A630A7-2598-41D7-B625-204CD906F5FB}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{32A630A7-2598-41D7-B625-204CD906F5FB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp.UniversalWindows", "Tests\MQTTnet.TestApp.UniversalWindows\MQTTnet.TestApp.UniversalWindows.csproj", "{FF1F72D6-9524-4422-9497-3CC0002216ED}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Frameworks\MQTTnet.NetStandard\MQTTnet.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Source\MQTTnet\MQTTnet.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1-BC3A-420A-BE9C-FA2401431CF9}" ProjectSection(SolutionItems) = preProject @@ -34,15 +34,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.NetCore", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.AspNetCore2", "Tests\MQTTnet.TestApp.AspNetCore2\MQTTnet.TestApp.AspNetCore2.csproj", "{C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Frameworks\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{12816BCC-AF9E-44A9-9AE5-C246AF2A0587}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Tests\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{998D04DD-7CB0-45F5-A393-E2495C16399E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedClient", "Extensions\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj", "{C400533A-8EBA-4F0B-BF4D-295C3708604B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedClient", "Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj", "{C400533A-8EBA-4F0B-BF4D-295C3708604B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs b/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs rename to Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs diff --git a/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj similarity index 87% rename from Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj rename to Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj index 1bc391b9..6b858954 100644 --- a/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj +++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -22,7 +22,7 @@ - + diff --git a/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs rename to Source/MQTTnet.AspnetCore/MqttHostedServer.cs diff --git a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs rename to Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs diff --git a/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs rename to Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs b/Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs rename to Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedEventArgs.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs rename to Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs rename to Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs rename to Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientStorage.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj b/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj similarity index 93% rename from Extensions/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj rename to Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj index 46e9a0af..3f050755 100644 --- a/Extensions/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj +++ b/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj @@ -25,7 +25,7 @@ - + diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessage.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessage.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessage.cs rename to Source/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessage.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessageBuilder.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessageBuilder.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessageBuilder.cs rename to Source/MQTTnet.Extensions.ManagedClient/ManagedMqttApplicationMessageBuilder.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs rename to Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs rename to Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs rename to Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs rename to Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs rename to Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs b/Source/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs rename to Source/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs diff --git a/Extensions/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs b/Source/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs rename to Source/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs diff --git a/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj similarity index 93% rename from Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj rename to Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj index 46e9a0af..3f050755 100644 --- a/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj +++ b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj @@ -25,7 +25,7 @@ - + diff --git a/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs similarity index 100% rename from Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs rename to Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs diff --git a/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c b/Source/MQTTnet.Extensions.Rpc/SampleCCode.c similarity index 100% rename from Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c rename to Source/MQTTnet.Extensions.Rpc/SampleCCode.c diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs rename to Source/MQTTnet/Adapter/IMqttChannelAdapter.cs diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/IMqttServerAdapter.cs b/Source/MQTTnet/Adapter/IMqttServerAdapter.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Adapter/IMqttServerAdapter.cs rename to Source/MQTTnet/Adapter/IMqttServerAdapter.cs diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs rename to Source/MQTTnet/Adapter/MqttChannelAdapter.cs diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttConnectingFailedException.cs b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Adapter/MqttConnectingFailedException.cs rename to Source/MQTTnet/Adapter/MqttConnectingFailedException.cs diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs b/Source/MQTTnet/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs rename to Source/MQTTnet/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs rename to Source/MQTTnet/Adapter/ReceivedMqttPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/ApplicationMessagePublisherExtensions.cs b/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/ApplicationMessagePublisherExtensions.cs rename to Source/MQTTnet/ApplicationMessagePublisherExtensions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs b/Source/MQTTnet/Channel/IMqttChannel.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs rename to Source/MQTTnet/Channel/IMqttChannel.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/IMqttClient.cs b/Source/MQTTnet/Client/IMqttClient.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/IMqttClient.cs rename to Source/MQTTnet/Client/IMqttClient.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientAdapterFactory.cs b/Source/MQTTnet/Client/IMqttClientAdapterFactory.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/IMqttClientAdapterFactory.cs rename to Source/MQTTnet/Client/IMqttClientAdapterFactory.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientChannelOptions.cs b/Source/MQTTnet/Client/IMqttClientChannelOptions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/IMqttClientChannelOptions.cs rename to Source/MQTTnet/Client/IMqttClientChannelOptions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientCredentials.cs b/Source/MQTTnet/Client/IMqttClientCredentials.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/IMqttClientCredentials.cs rename to Source/MQTTnet/Client/IMqttClientCredentials.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientFactory.cs b/Source/MQTTnet/Client/IMqttClientFactory.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/IMqttClientFactory.cs rename to Source/MQTTnet/Client/IMqttClientFactory.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientOptions.cs b/Source/MQTTnet/Client/IMqttClientOptions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/IMqttClientOptions.cs rename to Source/MQTTnet/Client/IMqttClientOptions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs rename to Source/MQTTnet/Client/MqttClient.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectResult.cs b/Source/MQTTnet/Client/MqttClientConnectResult.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectResult.cs rename to Source/MQTTnet/Client/MqttClientConnectResult.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectedEventArgs.cs b/Source/MQTTnet/Client/MqttClientConnectedEventArgs.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectedEventArgs.cs rename to Source/MQTTnet/Client/MqttClientConnectedEventArgs.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientCredentials.cs b/Source/MQTTnet/Client/MqttClientCredentials.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClientCredentials.cs rename to Source/MQTTnet/Client/MqttClientCredentials.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientDisconnectedEventArgs.cs b/Source/MQTTnet/Client/MqttClientDisconnectedEventArgs.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClientDisconnectedEventArgs.cs rename to Source/MQTTnet/Client/MqttClientDisconnectedEventArgs.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClientExtensions.cs rename to Source/MQTTnet/Client/MqttClientExtensions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptions.cs b/Source/MQTTnet/Client/MqttClientOptions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClientOptions.cs rename to Source/MQTTnet/Client/MqttClientOptions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs rename to Source/MQTTnet/Client/MqttClientOptionsBuilder.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs b/Source/MQTTnet/Client/MqttClientTcpOptions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs rename to Source/MQTTnet/Client/MqttClientTcpOptions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptionsExtensions.cs b/Source/MQTTnet/Client/MqttClientTcpOptionsExtensions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptionsExtensions.cs rename to Source/MQTTnet/Client/MqttClientTcpOptionsExtensions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientTlsOptions.cs b/Source/MQTTnet/Client/MqttClientTlsOptions.cs similarity index 58% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClientTlsOptions.cs rename to Source/MQTTnet/Client/MqttClientTlsOptions.cs index 6478b267..2536b231 100644 --- a/Frameworks/MQTTnet.NetStandard/Client/MqttClientTlsOptions.cs +++ b/Source/MQTTnet/Client/MqttClientTlsOptions.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; namespace MQTTnet.Client { @@ -13,5 +16,7 @@ namespace MQTTnet.Client public bool AllowUntrustedCertificates { get; set; } public List Certificates { get; set; } + + public Func CertificateValidationCallback { get; set; } } } diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs b/Source/MQTTnet/Client/MqttClientWebSocketOptions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs rename to Source/MQTTnet/Client/MqttClientWebSocketOptions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs b/Source/MQTTnet/Client/MqttPacketDispatcher.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs rename to Source/MQTTnet/Client/MqttPacketDispatcher.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketIdentifierProvider.cs b/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttPacketIdentifierProvider.cs rename to Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttSubscribeResult.cs b/Source/MQTTnet/Client/MqttSubscribeResult.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Client/MqttSubscribeResult.cs rename to Source/MQTTnet/Client/MqttSubscribeResult.cs diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetChildLogger.cs b/Source/MQTTnet/Diagnostics/IMqttNetChildLogger.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetChildLogger.cs rename to Source/MQTTnet/Diagnostics/IMqttNetChildLogger.cs diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs b/Source/MQTTnet/Diagnostics/IMqttNetLogger.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs rename to Source/MQTTnet/Diagnostics/IMqttNetLogger.cs diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetChildLogger.cs b/Source/MQTTnet/Diagnostics/MqttNetChildLogger.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetChildLogger.cs rename to Source/MQTTnet/Diagnostics/MqttNetChildLogger.cs diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetGlobalLogger.cs b/Source/MQTTnet/Diagnostics/MqttNetGlobalLogger.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetGlobalLogger.cs rename to Source/MQTTnet/Diagnostics/MqttNetGlobalLogger.cs diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogLevel.cs b/Source/MQTTnet/Diagnostics/MqttNetLogLevel.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogLevel.cs rename to Source/MQTTnet/Diagnostics/MqttNetLogLevel.cs diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessage.cs b/Source/MQTTnet/Diagnostics/MqttNetLogMessage.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessage.cs rename to Source/MQTTnet/Diagnostics/MqttNetLogMessage.cs diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs b/Source/MQTTnet/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs rename to Source/MQTTnet/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs b/Source/MQTTnet/Diagnostics/MqttNetLogger.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs rename to Source/MQTTnet/Diagnostics/MqttNetLogger.cs diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/TargetFrameworkInfoProvider.cs b/Source/MQTTnet/Diagnostics/TargetFrameworkInfoProvider.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Diagnostics/TargetFrameworkInfoProvider.cs rename to Source/MQTTnet/Diagnostics/TargetFrameworkInfoProvider.cs diff --git a/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationException.cs b/Source/MQTTnet/Exceptions/MqttCommunicationException.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationException.cs rename to Source/MQTTnet/Exceptions/MqttCommunicationException.cs diff --git a/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs b/Source/MQTTnet/Exceptions/MqttCommunicationTimedOutException.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs rename to Source/MQTTnet/Exceptions/MqttCommunicationTimedOutException.cs diff --git a/Frameworks/MQTTnet.NetStandard/Exceptions/MqttProtocolViolationException.cs b/Source/MQTTnet/Exceptions/MqttProtocolViolationException.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Exceptions/MqttProtocolViolationException.cs rename to Source/MQTTnet/Exceptions/MqttProtocolViolationException.cs diff --git a/Frameworks/MQTTnet.NetStandard/IApplicationMessagePublisher.cs b/Source/MQTTnet/IApplicationMessagePublisher.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/IApplicationMessagePublisher.cs rename to Source/MQTTnet/IApplicationMessagePublisher.cs diff --git a/Frameworks/MQTTnet.NetStandard/IApplicationMessageReceiver.cs b/Source/MQTTnet/IApplicationMessageReceiver.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/IApplicationMessageReceiver.cs rename to Source/MQTTnet/IApplicationMessageReceiver.cs diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs similarity index 91% rename from Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs rename to Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs index 5256da38..dd06c31a 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs +++ b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs @@ -16,9 +16,9 @@ namespace MQTTnet.Implementations switch (options.ChannelOptions) { - case MqttClientTcpOptions tcpOptions: + case MqttClientTcpOptions _: { - return new MqttChannelAdapter(new MqttTcpChannel(tcpOptions), serializer, logger); + return new MqttChannelAdapter(new MqttTcpChannel(options), serializer, logger); } case MqttClientWebSocketOptions webSocketOptions: diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs similarity index 89% rename from Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs rename to Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs index e8406d36..d7a030ab 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs @@ -11,32 +11,29 @@ using Windows.Networking.Sockets; using Windows.Security.Cryptography.Certificates; using MQTTnet.Channel; using MQTTnet.Client; +using MQTTnet.Server; namespace MQTTnet.Implementations { public class MqttTcpChannel : IMqttChannel { - // ReSharper disable once MemberCanBePrivate.Global - // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - public static int BufferSize { get; set; } = 4096; // Can be changed for fine tuning by library user. - - private readonly int _bufferSize = BufferSize; private readonly MqttClientTcpOptions _options; + private readonly int _bufferSize; private StreamSocket _socket; private Stream _readStream; private Stream _writeStream; - public MqttTcpChannel(MqttClientTcpOptions options) + public MqttTcpChannel(IMqttClientOptions clientOptions) { - _options = options ?? throw new ArgumentNullException(nameof(options)); - - _bufferSize = options.BufferSize; + _options = (MqttClientTcpOptions)clientOptions.ChannelOptions; + _bufferSize = _options.BufferSize; } - public MqttTcpChannel(StreamSocket socket) + public MqttTcpChannel(StreamSocket socket, IMqttServerOptions serverOptions) { _socket = socket ?? throw new ArgumentNullException(nameof(socket)); + _bufferSize = serverOptions.DefaultEndpointOptions.BufferSize; CreateStreams(); } @@ -97,7 +94,7 @@ namespace MQTTnet.Implementations TryDispose(_socket, () => _socket = null); } - private static Certificate LoadCertificate(MqttClientTcpOptions options) + private static Certificate LoadCertificate(IMqttClientChannelOptions options) { if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any()) { diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs similarity index 88% rename from Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs rename to Source/MQTTnet/Implementations/MqttTcpChannel.cs index 7e4148ff..20829be1 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -15,17 +15,19 @@ namespace MQTTnet.Implementations { public class MqttTcpChannel : IMqttChannel { + private readonly IMqttClientOptions _clientOptions; private readonly MqttClientTcpOptions _options; - + private Socket _socket; private Stream _stream; /// /// called on client sockets are created in connect /// - public MqttTcpChannel(MqttClientTcpOptions options) + public MqttTcpChannel(IMqttClientOptions clientOptions) { - _options = options ?? throw new ArgumentNullException(nameof(options)); + _clientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions)); + _options = (MqttClientTcpOptions)clientOptions.ChannelOptions; } /// @@ -39,6 +41,7 @@ namespace MQTTnet.Implementations CreateStream(sslStream); } + [Obsolete("There is a new callback at the TLS options. This one will be deleted soon.")] public static Func CustomCertificateValidationCallback { get; set; } public string Endpoint => _socket?.RemoteEndPoint?.ToString(); @@ -90,6 +93,13 @@ namespace MQTTnet.Implementations private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { + // Try the instance callback. + if (_options.TlsOptions.CertificateValidationCallback != null) + { + return _options.TlsOptions.CertificateValidationCallback(x509Certificate, chain, sslPolicyErrors,_clientOptions); + } + + // Try static callback. if (CustomCertificateValidationCallback != null) { return CustomCertificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _options); diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs similarity index 62% rename from Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs rename to Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs index 26759b2e..c42db2b6 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs @@ -12,7 +12,9 @@ namespace MQTTnet.Implementations public class MqttTcpServerAdapter : IMqttServerAdapter { private readonly IMqttNetChildLogger _logger; - private StreamSocketListener _defaultEndpointSocket; + + private IMqttServerOptions _options; + private StreamSocketListener _listener; public MqttTcpServerAdapter(IMqttNetChildLogger logger) { @@ -25,22 +27,21 @@ namespace MQTTnet.Implementations public async Task StartAsync(IMqttServerOptions options) { - if (options == null) throw new ArgumentNullException(nameof(options)); + _options = options ?? throw new ArgumentNullException(nameof(options)); - if (_defaultEndpointSocket != null) throw new InvalidOperationException("Server is already started."); + if (_listener != null) throw new InvalidOperationException("Server is already started."); if (options.DefaultEndpointOptions.IsEnabled) { - _defaultEndpointSocket = new StreamSocketListener(); + _listener = new StreamSocketListener(); // This also affects the client sockets. - _defaultEndpointSocket.Control.NoDelay = true; - _defaultEndpointSocket.Control.KeepAlive = true; - _defaultEndpointSocket.Control.QualityOfService = SocketQualityOfService.LowLatency; - _defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync; - - await _defaultEndpointSocket.BindServiceNameAsync(options.DefaultEndpointOptions.Port.ToString(), SocketProtectionLevel.PlainSocket); + _listener.Control.NoDelay = true; + _listener.Control.KeepAlive = true; + _listener.Control.QualityOfService = SocketQualityOfService.LowLatency; + _listener.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync; + await _listener.BindServiceNameAsync(options.DefaultEndpointOptions.Port.ToString(), SocketProtectionLevel.PlainSocket); } if (options.TlsEndpointOptions.IsEnabled) @@ -51,13 +52,13 @@ namespace MQTTnet.Implementations public Task StopAsync() { - if (_defaultEndpointSocket != null) + if (_listener != null) { - _defaultEndpointSocket.ConnectionReceived -= AcceptDefaultEndpointConnectionsAsync; + _listener.ConnectionReceived -= AcceptDefaultEndpointConnectionsAsync; } - _defaultEndpointSocket?.Dispose(); - _defaultEndpointSocket = null; + _listener?.Dispose(); + _listener = null; return Task.FromResult(0); } @@ -71,7 +72,7 @@ namespace MQTTnet.Implementations { try { - var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer(), _logger); + var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket, _options), new MqttPacketSerializer(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs rename to Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerListener.cs rename to Source/MQTTnet/Implementations/MqttTcpServerListener.cs diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs rename to Source/MQTTnet/Implementations/MqttWebSocketChannel.cs diff --git a/Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs b/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Internal/AsyncAutoResetEvent.cs rename to Source/MQTTnet/Internal/AsyncAutoResetEvent.cs diff --git a/Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs b/Source/MQTTnet/Internal/AsyncLock.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Internal/AsyncLock.cs rename to Source/MQTTnet/Internal/AsyncLock.cs diff --git a/Frameworks/MQTTnet.NetStandard/Internal/ExceptionHelper.cs b/Source/MQTTnet/Internal/ExceptionHelper.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Internal/ExceptionHelper.cs rename to Source/MQTTnet/Internal/ExceptionHelper.cs diff --git a/Frameworks/MQTTnet.NetStandard/Internal/MqttApplicationMessageExtensions.cs b/Source/MQTTnet/Internal/MqttApplicationMessageExtensions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Internal/MqttApplicationMessageExtensions.cs rename to Source/MQTTnet/Internal/MqttApplicationMessageExtensions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Internal/TaskExtensions.cs b/Source/MQTTnet/Internal/TaskExtensions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Internal/TaskExtensions.cs rename to Source/MQTTnet/Internal/TaskExtensions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs b/Source/MQTTnet/Internal/TestMqttChannel.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Internal/TestMqttChannel.cs rename to Source/MQTTnet/Internal/TestMqttChannel.cs diff --git a/Frameworks/MQTTnet.NetStandard/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj similarity index 100% rename from Frameworks/MQTTnet.NetStandard/MQTTnet.csproj rename to Source/MQTTnet/MQTTnet.csproj diff --git a/Frameworks/MQTTnet.NetStandard/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/MqttApplicationMessage.cs rename to Source/MQTTnet/MqttApplicationMessage.cs diff --git a/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/MqttApplicationMessageBuilder.cs rename to Source/MQTTnet/MqttApplicationMessageBuilder.cs diff --git a/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageExtensions.cs b/Source/MQTTnet/MqttApplicationMessageExtensions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/MqttApplicationMessageExtensions.cs rename to Source/MQTTnet/MqttApplicationMessageExtensions.cs diff --git a/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageReceivedEventArgs.cs b/Source/MQTTnet/MqttApplicationMessageReceivedEventArgs.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/MqttApplicationMessageReceivedEventArgs.cs rename to Source/MQTTnet/MqttApplicationMessageReceivedEventArgs.cs diff --git a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs b/Source/MQTTnet/MqttFactory.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/MqttFactory.cs rename to Source/MQTTnet/MqttFactory.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/IMqttPacketWithIdentifier.cs b/Source/MQTTnet/Packets/IMqttPacketWithIdentifier.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/IMqttPacketWithIdentifier.cs rename to Source/MQTTnet/Packets/IMqttPacketWithIdentifier.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttBasePacket.cs b/Source/MQTTnet/Packets/MqttBasePacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttBasePacket.cs rename to Source/MQTTnet/Packets/MqttBasePacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttBasePublishPacket.cs b/Source/MQTTnet/Packets/MqttBasePublishPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttBasePublishPacket.cs rename to Source/MQTTnet/Packets/MqttBasePublishPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttConnAckPacket.cs b/Source/MQTTnet/Packets/MqttConnAckPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttConnAckPacket.cs rename to Source/MQTTnet/Packets/MqttConnAckPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttConnectPacket.cs b/Source/MQTTnet/Packets/MqttConnectPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttConnectPacket.cs rename to Source/MQTTnet/Packets/MqttConnectPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttDisconnectPacket.cs b/Source/MQTTnet/Packets/MqttDisconnectPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttDisconnectPacket.cs rename to Source/MQTTnet/Packets/MqttDisconnectPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPingReqPacket.cs b/Source/MQTTnet/Packets/MqttPingReqPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttPingReqPacket.cs rename to Source/MQTTnet/Packets/MqttPingReqPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPingRespPacket.cs b/Source/MQTTnet/Packets/MqttPingRespPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttPingRespPacket.cs rename to Source/MQTTnet/Packets/MqttPingRespPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubAckPacket.cs b/Source/MQTTnet/Packets/MqttPubAckPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttPubAckPacket.cs rename to Source/MQTTnet/Packets/MqttPubAckPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubCompPacket.cs b/Source/MQTTnet/Packets/MqttPubCompPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttPubCompPacket.cs rename to Source/MQTTnet/Packets/MqttPubCompPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRecPacket.cs b/Source/MQTTnet/Packets/MqttPubRecPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttPubRecPacket.cs rename to Source/MQTTnet/Packets/MqttPubRecPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRelPacket.cs b/Source/MQTTnet/Packets/MqttPubRelPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttPubRelPacket.cs rename to Source/MQTTnet/Packets/MqttPubRelPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPublishPacket.cs b/Source/MQTTnet/Packets/MqttPublishPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttPublishPacket.cs rename to Source/MQTTnet/Packets/MqttPublishPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttSubAckPacket.cs b/Source/MQTTnet/Packets/MqttSubAckPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttSubAckPacket.cs rename to Source/MQTTnet/Packets/MqttSubAckPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttSubscribePacket.cs b/Source/MQTTnet/Packets/MqttSubscribePacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttSubscribePacket.cs rename to Source/MQTTnet/Packets/MqttSubscribePacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubAckPacket.cs b/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubAckPacket.cs rename to Source/MQTTnet/Packets/MqttUnsubAckPacket.cs diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs b/Source/MQTTnet/Packets/MqttUnsubscribe.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs rename to Source/MQTTnet/Packets/MqttUnsubscribe.cs diff --git a/Frameworks/MQTTnet.NetStandard/Protocol/MqttConnectReturnCode.cs b/Source/MQTTnet/Protocol/MqttConnectReturnCode.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Protocol/MqttConnectReturnCode.cs rename to Source/MQTTnet/Protocol/MqttConnectReturnCode.cs diff --git a/Frameworks/MQTTnet.NetStandard/Protocol/MqttControlPacketType.cs b/Source/MQTTnet/Protocol/MqttControlPacketType.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Protocol/MqttControlPacketType.cs rename to Source/MQTTnet/Protocol/MqttControlPacketType.cs diff --git a/Frameworks/MQTTnet.NetStandard/Protocol/MqttQualityOfServiceLevel.cs b/Source/MQTTnet/Protocol/MqttQualityOfServiceLevel.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Protocol/MqttQualityOfServiceLevel.cs rename to Source/MQTTnet/Protocol/MqttQualityOfServiceLevel.cs diff --git a/Frameworks/MQTTnet.NetStandard/Protocol/MqttSubscribeReturnCode.cs b/Source/MQTTnet/Protocol/MqttSubscribeReturnCode.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Protocol/MqttSubscribeReturnCode.cs rename to Source/MQTTnet/Protocol/MqttSubscribeReturnCode.cs diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/ByteReader.cs b/Source/MQTTnet/Serializer/ByteReader.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Serializer/ByteReader.cs rename to Source/MQTTnet/Serializer/ByteReader.cs diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/ByteWriter.cs b/Source/MQTTnet/Serializer/ByteWriter.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Serializer/ByteWriter.cs rename to Source/MQTTnet/Serializer/ByteWriter.cs diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/Extensions.cs b/Source/MQTTnet/Serializer/Extensions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Serializer/Extensions.cs rename to Source/MQTTnet/Serializer/Extensions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs b/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs rename to Source/MQTTnet/Serializer/IMqttPacketSerializer.cs diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttFixedHeader.cs b/Source/MQTTnet/Serializer/MqttFixedHeader.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Serializer/MqttFixedHeader.cs rename to Source/MQTTnet/Serializer/MqttFixedHeader.cs diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketBodyReader.cs b/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketBodyReader.cs rename to Source/MQTTnet/Serializer/MqttPacketBodyReader.cs diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs b/Source/MQTTnet/Serializer/MqttPacketReader.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs rename to Source/MQTTnet/Serializer/MqttPacketReader.cs diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs b/Source/MQTTnet/Serializer/MqttPacketSerializer.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs rename to Source/MQTTnet/Serializer/MqttPacketSerializer.cs diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs b/Source/MQTTnet/Serializer/MqttPacketWriter.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs rename to Source/MQTTnet/Serializer/MqttPacketWriter.cs diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttProtocolVersion.cs b/Source/MQTTnet/Serializer/MqttProtocolVersion.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Serializer/MqttProtocolVersion.cs rename to Source/MQTTnet/Serializer/MqttProtocolVersion.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/CheckSubscriptionsResult.cs b/Source/MQTTnet/Server/CheckSubscriptionsResult.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/CheckSubscriptionsResult.cs rename to Source/MQTTnet/Server/CheckSubscriptionsResult.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/GetOrCreateClientSessionResult.cs b/Source/MQTTnet/Server/GetOrCreateClientSessionResult.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/GetOrCreateClientSessionResult.cs rename to Source/MQTTnet/Server/GetOrCreateClientSessionResult.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttClientSessionStatus.cs b/Source/MQTTnet/Server/IMqttClientSessionStatus.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/IMqttClientSessionStatus.cs rename to Source/MQTTnet/Server/IMqttClientSessionStatus.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs b/Source/MQTTnet/Server/IMqttServer.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs rename to Source/MQTTnet/Server/IMqttServer.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerFactory.cs b/Source/MQTTnet/Server/IMqttServerFactory.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/IMqttServerFactory.cs rename to Source/MQTTnet/Server/IMqttServerFactory.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs b/Source/MQTTnet/Server/IMqttServerOptions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs rename to Source/MQTTnet/Server/IMqttServerOptions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerStorage.cs b/Source/MQTTnet/Server/IMqttServerStorage.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/IMqttServerStorage.cs rename to Source/MQTTnet/Server/IMqttServerStorage.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttApplicationMessageInterceptorContext.cs b/Source/MQTTnet/Server/MqttApplicationMessageInterceptorContext.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttApplicationMessageInterceptorContext.cs rename to Source/MQTTnet/Server/MqttApplicationMessageInterceptorContext.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientConnectedEventArgs.cs b/Source/MQTTnet/Server/MqttClientConnectedEventArgs.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientConnectedEventArgs.cs rename to Source/MQTTnet/Server/MqttClientConnectedEventArgs.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientDisconnectType.cs b/Source/MQTTnet/Server/MqttClientDisconnectType.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientDisconnectType.cs rename to Source/MQTTnet/Server/MqttClientDisconnectType.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientDisconnectedEventArgs.cs b/Source/MQTTnet/Server/MqttClientDisconnectedEventArgs.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientDisconnectedEventArgs.cs rename to Source/MQTTnet/Server/MqttClientDisconnectedEventArgs.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs b/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs rename to Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientMessageQueueInterceptorContext.cs b/Source/MQTTnet/Server/MqttClientMessageQueueInterceptorContext.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientMessageQueueInterceptorContext.cs rename to Source/MQTTnet/Server/MqttClientMessageQueueInterceptorContext.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Source/MQTTnet/Server/MqttClientPendingMessagesQueue.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs rename to Source/MQTTnet/Server/MqttClientPendingMessagesQueue.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs rename to Source/MQTTnet/Server/MqttClientSession.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionStatus.cs b/Source/MQTTnet/Server/MqttClientSessionStatus.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionStatus.cs rename to Source/MQTTnet/Server/MqttClientSessionStatus.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs rename to Source/MQTTnet/Server/MqttClientSessionsManager.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscribeResult.cs b/Source/MQTTnet/Server/MqttClientSubscribeResult.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscribeResult.cs rename to Source/MQTTnet/Server/MqttClientSubscribeResult.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscribedTopicEventArgs.cs b/Source/MQTTnet/Server/MqttClientSubscribedTopicEventArgs.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscribedTopicEventArgs.cs rename to Source/MQTTnet/Server/MqttClientSubscribedTopicEventArgs.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientSubscriptionsManager.cs rename to Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientUnSubscribedTopicEventArgs.cs b/Source/MQTTnet/Server/MqttClientUnSubscribedTopicEventArgs.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttClientUnSubscribedTopicEventArgs.cs rename to Source/MQTTnet/Server/MqttClientUnSubscribedTopicEventArgs.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttConnectionValidatorContext.cs b/Source/MQTTnet/Server/MqttConnectionValidatorContext.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttConnectionValidatorContext.cs rename to Source/MQTTnet/Server/MqttConnectionValidatorContext.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttPendingMessagesOverflowStrategy.cs b/Source/MQTTnet/Server/MqttPendingMessagesOverflowStrategy.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttPendingMessagesOverflowStrategy.cs rename to Source/MQTTnet/Server/MqttPendingMessagesOverflowStrategy.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttRetainedMessagesManager.cs rename to Source/MQTTnet/Server/MqttRetainedMessagesManager.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttServer.cs rename to Source/MQTTnet/Server/MqttServer.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs b/Source/MQTTnet/Server/MqttServerOptions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttServerOptions.cs rename to Source/MQTTnet/Server/MqttServerOptions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttServerOptionsBuilder.cs rename to Source/MQTTnet/Server/MqttServerOptionsBuilder.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointBaseOptions.cs b/Source/MQTTnet/Server/MqttServerTcpEndpointBaseOptions.cs similarity index 84% rename from Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointBaseOptions.cs rename to Source/MQTTnet/Server/MqttServerTcpEndpointBaseOptions.cs index 8ef8d41b..45df2780 100644 --- a/Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointBaseOptions.cs +++ b/Source/MQTTnet/Server/MqttServerTcpEndpointBaseOptions.cs @@ -10,6 +10,10 @@ namespace MQTTnet.Server public int ConnectionBacklog { get; set; } = 10; +#if WINDOWS_UWP + public int BufferSize { get; set; } = 4096; +#endif + public IPAddress BoundInterNetworkAddress { get; set; } = IPAddress.Any; public IPAddress BoundInterNetworkV6Address { get; set; } = IPAddress.IPv6Any; diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointOptions.cs b/Source/MQTTnet/Server/MqttServerTcpEndpointOptions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttServerTcpEndpointOptions.cs rename to Source/MQTTnet/Server/MqttServerTcpEndpointOptions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsTcpEndpointOptions.cs b/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttServerTlsTcpEndpointOptions.cs rename to Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttSubscribeResult.cs b/Source/MQTTnet/Server/MqttSubscribeResult.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttSubscribeResult.cs rename to Source/MQTTnet/Server/MqttSubscribeResult.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttSubscriptionInterceptorContext.cs b/Source/MQTTnet/Server/MqttSubscriptionInterceptorContext.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttSubscriptionInterceptorContext.cs rename to Source/MQTTnet/Server/MqttSubscriptionInterceptorContext.cs diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs b/Source/MQTTnet/Server/MqttTopicFilterComparer.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/Server/MqttTopicFilterComparer.cs rename to Source/MQTTnet/Server/MqttTopicFilterComparer.cs diff --git a/Frameworks/MQTTnet.NetStandard/TopicFilter.cs b/Source/MQTTnet/TopicFilter.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/TopicFilter.cs rename to Source/MQTTnet/TopicFilter.cs diff --git a/Frameworks/MQTTnet.NetStandard/TopicFilterBuilder.cs b/Source/MQTTnet/TopicFilterBuilder.cs similarity index 100% rename from Frameworks/MQTTnet.NetStandard/TopicFilterBuilder.cs rename to Source/MQTTnet/TopicFilterBuilder.cs diff --git a/Frameworks/MQTTnet.NetStandard/codeSigningKey.pfx b/Source/MQTTnet/codeSigningKey.pfx similarity index 100% rename from Frameworks/MQTTnet.NetStandard/codeSigningKey.pfx rename to Source/MQTTnet/codeSigningKey.pfx diff --git a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index 4db064ff..5e612fb9 100644 --- a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs b/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs index 740b83ee..1f29c97a 100644 --- a/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs @@ -14,8 +14,6 @@ namespace MQTTnet.Benchmarks { private IMqttServer _mqttServer; private IMqttChannel _serverChannel; - - private IMqttChannel _clientChannel; [GlobalSetup] @@ -23,7 +21,7 @@ namespace MQTTnet.Benchmarks { var factory = new MqttFactory(); var tcpServer = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger()); - tcpServer.ClientAccepted += (sender, args) => _serverChannel = (IMqttChannel)args.Client.GetType().GetField("_channel", System.Reflection.BindingFlags.NonPublic| System.Reflection.BindingFlags.Instance).GetValue(args.Client); + tcpServer.ClientAccepted += (sender, args) => _serverChannel = (IMqttChannel)args.Client.GetType().GetField("_channel", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(args.Client); _mqttServer = factory.CreateMqttServer(new[] { tcpServer }, new MqttNetLogger()); @@ -33,8 +31,8 @@ namespace MQTTnet.Benchmarks var clientOptions = new MqttClientOptionsBuilder() .WithTcpServer("localhost").Build(); - var tcpOptions = (MqttClientTcpOptions) clientOptions.ChannelOptions; - _clientChannel = new MqttTcpChannel(tcpOptions); + var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions; + _clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions }); _clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult(); } @@ -65,7 +63,7 @@ namespace MQTTnet.Benchmarks private async Task WriteAsync(int iterations, int size) { await Task.Yield(); - + for (var i = 0; i < iterations; i++) { await _serverChannel.WriteAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false); diff --git a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj index 52e3326c..40697e94 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj +++ b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj index cd38d16c..c587ecc8 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.0 @@ -14,7 +14,8 @@ - + + diff --git a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj index 50501637..2a738a08 100644 --- a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj +++ b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj @@ -1,4 +1,4 @@ - + Exe @@ -15,8 +15,8 @@ - - + + diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj index 0b6cad83..d7090834 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MQTTnet.TestApp.UniversalWindows.csproj @@ -127,15 +127,15 @@ - + {C400533A-8EBA-4F0B-BF4D-295C3708604B} MQTTnet.Extensions.ManagedClient - + {c444e9c8-95fa-430e-9126-274129de16cd} MQTTnet.Extensions.Rpc - + {3587e506-55a2-4eb3-99c7-dc01e42d25d2} MQTTnet From 26eed30dbf42865f7d06d302d0c24fd21c445566 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Mon, 11 Jun 2018 00:26:38 +0200 Subject: [PATCH 099/138] Added custom exception for gracefully closed exceptions. --- Build/MQTTnet.nuspec | 2 ++ .../MqttCommunicationClosedGracefullyException.cs | 6 ++++++ Source/MQTTnet/Implementations/MqttTcpServerListener.cs | 2 ++ Source/MQTTnet/Internal/ExceptionHelper.cs | 2 +- Source/MQTTnet/Server/MqttClientSession.cs | 9 ++++++++- Tests/MQTTnet.TestApp.NetCore/ClientTest.cs | 2 +- 6 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 Source/MQTTnet/Exceptions/MqttCommunicationClosedGracefullyException.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 96389fa2..25c31fb5 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -27,8 +27,10 @@ * [Server] Keep alive checking is now suspended while large packages are being received (and thus the client is connected). Keep alive checking continues after a large packet is received completely. * [Server] Rewritten the _ConnectedClients_ API and added new features for disconnecting and Endpoint information (IP etc.). * [Server] Added settings for disabling persistent sessions and defining a max pending messages queue size per session. +* [Server] Persistent sessions are disabled by default (BREAKING CHANGE!). * [Server] Added a new interceptor which is invoked before a new message is added to the client queue. * [Server] Added support for Linux servers by dividing IPv4 and IPv6 support and adding new options (BREAKING CHANGE!). +* [Server] Gracefully closed connections are no longer reported as warnings. Copyright Christian Kratky 2016-2018 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 diff --git a/Source/MQTTnet/Exceptions/MqttCommunicationClosedGracefullyException.cs b/Source/MQTTnet/Exceptions/MqttCommunicationClosedGracefullyException.cs new file mode 100644 index 00000000..21a66b90 --- /dev/null +++ b/Source/MQTTnet/Exceptions/MqttCommunicationClosedGracefullyException.cs @@ -0,0 +1,6 @@ +namespace MQTTnet.Exceptions +{ + public class MqttCommunicationClosedGracefullyException : MqttCommunicationException + { + } +} diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs index 4bf2b081..fccd77f3 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs @@ -76,6 +76,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 clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } diff --git a/Source/MQTTnet/Internal/ExceptionHelper.cs b/Source/MQTTnet/Internal/ExceptionHelper.cs index 2b504511..f3cadc91 100644 --- a/Source/MQTTnet/Internal/ExceptionHelper.cs +++ b/Source/MQTTnet/Internal/ExceptionHelper.cs @@ -6,7 +6,7 @@ namespace MQTTnet.Internal { public static void ThrowGracefulSocketClose() { - throw new MqttCommunicationException("Connection gracefully closed from the remote party."); + throw new MqttCommunicationClosedGracefullyException(); } } } diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index cb1043be..ed029432 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -100,7 +100,14 @@ namespace MQTTnet.Server { if (exception is MqttCommunicationException) { - _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId); + if (exception is MqttCommunicationClosedGracefullyException) + { + _logger.Verbose("Client '{0}': Connection closed gracefully.", ClientId); ; + } + else + { + _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId); + } } else { diff --git a/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs index b943bc3f..98e7f0b1 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs @@ -20,7 +20,7 @@ namespace MQTTnet.TestApp.NetCore { ChannelOptions = new MqttClientTcpOptions { - Server = "localhost" + Server = "127.0.0.1" } }; From 619268567131b1d3cd7e3ec77b7b8f43d04c3731 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 15 Jun 2018 00:19:51 +0200 Subject: [PATCH 100/138] Remove batch sending of messages to prevent message reordering and several required awaits. --- .../IManagedMqttClient.cs | 2 +- .../ManagedMqttClient.cs | 22 ++--- Source/MQTTnet/Adapter/IMqttChannelAdapter.cs | 3 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 13 +-- .../ApplicationMessagePublisherExtensions.cs | 21 ++++- Source/MQTTnet/Client/MqttClient.cs | 89 ++++++++----------- .../MQTTnet/IApplicationMessagePublisher.cs | 5 +- .../MQTTnet/Internal/AsyncAutoResetEvent.cs | 13 ++- .../Server/MqttClientPendingMessagesQueue.cs | 2 +- Source/MQTTnet/Server/MqttClientSession.cs | 14 +-- .../Server/MqttClientSessionsManager.cs | 31 ++++--- Source/MQTTnet/Server/MqttServer.cs | 9 +- .../ChannelAdapterBenchmark.cs | 2 +- .../TestMqttCommunicationAdapter.cs | 7 +- 14 files changed, 106 insertions(+), 127 deletions(-) diff --git a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs index ff9f5303..e4eaa687 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs @@ -21,6 +21,6 @@ namespace MQTTnet.Extensions.ManagedClient Task SubscribeAsync(IEnumerable topicFilters); Task UnsubscribeAsync(IEnumerable topics); - Task PublishAsync(IEnumerable applicationMessages); + Task PublishAsync(ManagedMqttApplicationMessage applicationMessages); } } \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index a9693a80..9bd14b21 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -99,27 +99,23 @@ namespace MQTTnet.Extensions.ManagedClient return Task.FromResult(0); } - public Task PublishAsync(IEnumerable applicationMessages) + public Task PublishAsync(MqttApplicationMessage applicationMessage) { - if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - return PublishAsync(applicationMessages.Select(m => - new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(m).Build())); + return PublishAsync(new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(applicationMessage).Build()); } - public async Task PublishAsync(IEnumerable applicationMessages) + public async Task PublishAsync(ManagedMqttApplicationMessage applicationMessage) { - if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - foreach (var applicationMessage in applicationMessages) + if (_storageManager != null) { - if (_storageManager != null) - { - await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false); - } - - _messageQueue.Add(applicationMessage); + await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false); } + + _messageQueue.Add(applicationMessage); } public async Task SubscribeAsync(IEnumerable topicFilters) diff --git a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs index 3a99add2..e026e7ba 100644 --- a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MQTTnet.Packets; @@ -21,7 +20,7 @@ namespace MQTTnet.Adapter Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken); - Task SendPacketsAsync(TimeSpan timeout, IEnumerable packets, CancellationToken cancellationToken); + Task SendPacketAsync(TimeSpan timeout, MqttBasePacket packet, CancellationToken cancellationToken); Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken); } diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 605997a3..4fc681ad 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Net.Sockets; using System.Runtime.InteropServices; @@ -59,17 +58,7 @@ namespace MQTTnet.Adapter Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, cancellationToken)); } - public async Task SendPacketsAsync(TimeSpan timeout, IEnumerable packets, CancellationToken cancellationToken) - { - ThrowIfDisposed(); - - foreach (var packet in packets) - { - await SendPacketAsync(timeout, cancellationToken, packet).ConfigureAwait(false); - } - } - - private Task SendPacketAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket packet) + public Task SendPacketAsync(TimeSpan timeout, MqttBasePacket packet, CancellationToken cancellationToken) { return ExecuteAndWrapExceptionAsync(() => { diff --git a/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs b/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs index 4479f22d..dd166627 100644 --- a/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs +++ b/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using MQTTnet.Protocol; @@ -6,19 +7,33 @@ namespace MQTTnet { public static class ApplicationMessagePublisherExtensions { - public static Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages) + public static async Task PublishAsync(this IApplicationMessagePublisher publisher, IEnumerable applicationMessages) { if (publisher == null) throw new ArgumentNullException(nameof(publisher)); if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); - return publisher.PublishAsync(applicationMessages); + foreach (var applicationMessage in applicationMessages) + { + await publisher.PublishAsync(applicationMessage); + } + } + + public static async Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages) + { + if (publisher == null) throw new ArgumentNullException(nameof(publisher)); + if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); + + foreach (var applicationMessage in applicationMessages) + { + await publisher.PublishAsync(applicationMessage); + } } public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic) { if (publisher == null) throw new ArgumentNullException(nameof(publisher)); if (topic == null) throw new ArgumentNullException(nameof(topic)); - + return publisher.PublishAsync(builder => builder .WithTopic(topic) ); diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index d93a356d..79a0ecff 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -19,7 +19,7 @@ namespace MQTTnet.Client private readonly Stopwatch _sendTracker = new Stopwatch(); private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1); private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); - + private readonly IMqttClientAdapterFactory _adapterFactory; private readonly IMqttNetChildLogger _logger; @@ -145,55 +145,32 @@ namespace MQTTnet.Client await SendAndReceiveAsync(unsubscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false); } - public async Task PublishAsync(IEnumerable applicationMessages) + public Task PublishAsync(MqttApplicationMessage applicationMessage) { ThrowIfNotConnected(); - var publishPackets = applicationMessages.Select(m => m.ToPublishPacket()); - var packetGroups = publishPackets.GroupBy(p => p.QualityOfServiceLevel).OrderBy(g => g.Key); + var publishPacket = applicationMessage.ToPublishPacket(); - foreach (var qosGroup in packetGroups) + switch (applicationMessage.QualityOfServiceLevel) { - switch (qosGroup.Key) - { - case MqttQualityOfServiceLevel.AtMostOnce: - { - // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] - await SendAsync(qosGroup, _cancellationTokenSource.Token).ConfigureAwait(false); - break; - } - case MqttQualityOfServiceLevel.AtLeastOnce: - { - foreach (var publishPacket in qosGroup) - { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); - await SendAndReceiveAsync(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false); - } - - break; - } - case MqttQualityOfServiceLevel.ExactlyOnce: - { - foreach (var publishPacket in qosGroup) - { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); - - var pubRecPacket = await SendAndReceiveAsync(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false); - var pubRelPacket = new MqttPubRelPacket - { - PacketIdentifier = pubRecPacket.PacketIdentifier - }; - - await SendAndReceiveAsync(pubRelPacket, _cancellationTokenSource.Token).ConfigureAwait(false); - } - - break; - } - default: - { - throw new InvalidOperationException(); - } - } + case MqttQualityOfServiceLevel.AtMostOnce: + { + // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] + return SendAsync(publishPacket, _cancellationTokenSource.Token); + } + case MqttQualityOfServiceLevel.AtLeastOnce: + { + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); + return SendAndReceiveAsync(publishPacket, _cancellationTokenSource.Token); + } + case MqttQualityOfServiceLevel.ExactlyOnce: + { + return PublishExactlyOnce(publishPacket, _cancellationTokenSource.Token); + } + default: + { + throw new NotSupportedException(); + } } } @@ -295,11 +272,6 @@ namespace MQTTnet.Client } private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken) - { - return SendAsync(new[] { packet }, cancellationToken); - } - - private Task SendAsync(IEnumerable packets, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -307,7 +279,7 @@ namespace MQTTnet.Client } _sendTracker.Restart(); - return _adapter.SendPacketsAsync(_options.CommunicationTimeout, packets, cancellationToken); + return _adapter.SendPacketAsync(_options.CommunicationTimeout, packet, cancellationToken); } private async Task SendAndReceiveAsync(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket @@ -328,7 +300,7 @@ namespace MQTTnet.Client var packetAwaiter = _packetDispatcher.AddPacketAwaiter(identifier); try { - await _adapter.SendPacketsAsync(_options.CommunicationTimeout, new[] { requestPacket }, cancellationToken).ConfigureAwait(false); + await _adapter.SendPacketAsync(_options.CommunicationTimeout, requestPacket, cancellationToken).ConfigureAwait(false); var respone = await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, _options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); return (TResponsePacket)respone; @@ -498,6 +470,19 @@ namespace MQTTnet.Client return SendAsync(response, cancellationToken); } + private async Task PublishExactlyOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + { + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); + + var pubRecPacket = await SendAndReceiveAsync(publishPacket, cancellationToken).ConfigureAwait(false); + var pubRelPacket = new MqttPubRelPacket + { + PacketIdentifier = pubRecPacket.PacketIdentifier + }; + + await SendAndReceiveAsync(pubRelPacket, cancellationToken).ConfigureAwait(false); + } + private void StartReceivingPackets(CancellationToken cancellationToken) { _packetReceiverTask = Task.Run( diff --git a/Source/MQTTnet/IApplicationMessagePublisher.cs b/Source/MQTTnet/IApplicationMessagePublisher.cs index e137669e..c47893af 100644 --- a/Source/MQTTnet/IApplicationMessagePublisher.cs +++ b/Source/MQTTnet/IApplicationMessagePublisher.cs @@ -1,10 +1,9 @@ -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace MQTTnet { public interface IApplicationMessagePublisher { - Task PublishAsync(IEnumerable applicationMessages); + Task PublishAsync(MqttApplicationMessage applicationMessage); } } diff --git a/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs b/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs index f20733d8..132e7e05 100644 --- a/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs +++ b/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs @@ -47,18 +47,17 @@ namespace MQTTnet.Internal _isSignaled = false; return true; } - else if (timeout == TimeSpan.Zero) + + if (timeout == TimeSpan.Zero) { return _isSignaled; } - else - { - tcs = new TaskCompletionSource(); - _waiters.AddLast(tcs); - } + + tcs = new TaskCompletionSource(); + _waiters.AddLast(tcs); } - Task winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).ConfigureAwait(false); + var winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).ConfigureAwait(false); if (winner == tcs.Task) { // The task was signaled. diff --git a/Source/MQTTnet/Server/MqttClientPendingMessagesQueue.cs b/Source/MQTTnet/Server/MqttClientPendingMessagesQueue.cs index 15d4af90..ff43e6fc 100644 --- a/Source/MQTTnet/Server/MqttClientPendingMessagesQueue.cs +++ b/Source/MQTTnet/Server/MqttClientPendingMessagesQueue.cs @@ -115,7 +115,7 @@ namespace MQTTnet.Server return; } - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { packet }, cancellationToken).ConfigureAwait(false); + await adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, packet, cancellationToken).ConfigureAwait(false); _logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); } diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index ed029432..28e97f02 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -245,7 +245,7 @@ namespace MQTTnet.Server if (packet is MqttPingReqPacket) { - return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { new MqttPingRespPacket() }, cancellationToken); + return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, new MqttPingRespPacket(), cancellationToken); } if (packet is MqttPubRelPacket pubRelPacket) @@ -260,7 +260,7 @@ namespace MQTTnet.Server PacketIdentifier = pubRecPacket.PacketIdentifier }; - return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { responsePacket }, cancellationToken); + return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, responsePacket, cancellationToken); } if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) @@ -308,7 +308,7 @@ namespace MQTTnet.Server private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) { var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket); - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { subscribeResult.ResponsePacket }, cancellationToken).ConfigureAwait(false); + await adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false); if (subscribeResult.CloseConnection) { @@ -322,7 +322,7 @@ namespace MQTTnet.Server private Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) { var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket); - return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { unsubscribeResult }, cancellationToken); + return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, unsubscribeResult, cancellationToken); } private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) @@ -356,7 +356,7 @@ namespace MQTTnet.Server _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage); var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }; - return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken); + return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken); } private Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) @@ -365,13 +365,13 @@ namespace MQTTnet.Server _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage); var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; - return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken); + return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken); } private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) { var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }; - return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken); + return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken); } private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index dadf32a2..c52c706c 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -60,13 +60,12 @@ namespace MQTTnet.Server var connectReturnCode = ValidateConnection(connectPacket); if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { - await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] - { + await clientAdapter.SendPacketAsync(_options.DefaultCommunicationTimeout, new MqttConnAckPacket { ConnectReturnCode = connectReturnCode - } - }, cancellationToken).ConfigureAwait(false); + }, + cancellationToken).ConfigureAwait(false); return; } @@ -74,14 +73,13 @@ namespace MQTTnet.Server var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false); var clientSession = result.Session; - await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] - { + await clientAdapter.SendPacketAsync(_options.DefaultCommunicationTimeout, new MqttConnAckPacket { ConnectReturnCode = connectReturnCode, IsSessionPresent = result.IsExistingSession - } - }, cancellationToken).ConfigureAwait(false); + }, + cancellationToken).ConfigureAwait(false); Server.OnClientConnected(clientId); @@ -242,14 +240,19 @@ namespace MQTTnet.Server try { var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage); - if (interceptorContext.CloseConnection) + if (interceptorContext != null) { - senderClientSession.Stop(MqttClientDisconnectType.NotClean); - } + if (interceptorContext.CloseConnection) + { + senderClientSession.Stop(MqttClientDisconnectType.NotClean); + } - if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish) - { - return; + if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish) + { + return; + } + + applicationMessage = interceptorContext.ApplicationMessage; } Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage); diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index b13fdf73..a38dfd21 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -59,16 +59,13 @@ namespace MQTTnet.Server return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters); } - public Task PublishAsync(IEnumerable applicationMessages) + public Task PublishAsync(MqttApplicationMessage applicationMessage) { - if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started."); - foreach (var applicationMessage in applicationMessages) - { - _clientSessionsManager.StartDispatchApplicationMessage(null, applicationMessage); - } + _clientSessionsManager.StartDispatchApplicationMessage(null, applicationMessage); return Task.FromResult(0); } diff --git a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs index 577d6960..20b581c0 100644 --- a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -65,7 +65,7 @@ namespace MQTTnet.Benchmarks for (var i = 0; i < 10000; i++) { - _channelAdapter.SendPacketsAsync(TimeSpan.FromSeconds(15), new[] { _packet }, CancellationToken.None).GetAwaiter().GetResult(); + _channelAdapter.SendPacketAsync(TimeSpan.FromSeconds(15), _packet, CancellationToken.None).GetAwaiter().GetResult(); } _stream.Position = 0; diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index 8143728f..ffe2a77a 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -36,14 +36,11 @@ namespace MQTTnet.Core.Tests return Task.FromResult(0); } - public Task SendPacketsAsync(TimeSpan timeout, IEnumerable packets, CancellationToken cancellationToken) + public Task SendPacketAsync(TimeSpan timeout, MqttBasePacket packet, CancellationToken cancellationToken) { ThrowIfPartnerIsNull(); - foreach (var packet in packets) - { - Partner.EnqueuePacketInternal(packet); - } + Partner.EnqueuePacketInternal(packet); return Task.FromResult(0); } From caea7910b4b11b36486836fdc6f31a6cb96cd71b Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 16 Jun 2018 14:30:02 +0200 Subject: [PATCH 101/138] Refactor message processing and async/await usage. --- Build/MQTTnet.Extensions.ManagedClient.nuspec | 4 +- Build/MQTTnet.Extensions.Rpc.nuspec | 4 +- Build/MQTTnet.nuspec | 4 +- Source/MQTTnet/Adapter/IMqttChannelAdapter.cs | 2 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 176 ++++++----- Source/MQTTnet/Client/MqttClient.cs | 38 ++- .../TargetFrameworkInfoProvider.cs | 2 + .../MQTTnet/Implementations/MqttTcpChannel.cs | 2 +- .../Implementations/MqttTcpServerAdapter.cs | 2 +- .../Implementations/MqttTcpServerListener.cs | 7 +- Source/MQTTnet/Internal/TaskExtensions.cs | 4 +- Source/MQTTnet/MQTTnet.csproj | 5 +- Source/MQTTnet/Serializer/ByteReader.cs | 48 --- Source/MQTTnet/Serializer/ByteWriter.cs | 36 --- Source/MQTTnet/Serializer/MqttPacketReader.cs | 12 +- .../Serializer/MqttPacketSerializer.cs | 228 +++++++------- Source/MQTTnet/Serializer/MqttPacketWriter.cs | 128 ++++++-- ...ue.cs => MqttClientPendingPacketsQueue.cs} | 8 +- Source/MQTTnet/Server/MqttClientSession.cs | 41 ++- .../Server/MqttClientSessionsManager.cs | 278 +++++++++--------- .../Server/MqttEnqueuedApplicationMessage.cs | 15 + Source/MQTTnet/Server/MqttServer.cs | 11 +- ...esult.cs => PrepareClientSessionResult.cs} | 2 +- .../ChannelAdapterBenchmark.cs | 2 +- Tests/MQTTnet.Core.Tests/ByteReaderTests.cs | 30 -- Tests/MQTTnet.Core.Tests/ByteWriterTests.cs | 51 ---- Tests/MQTTnet.Core.Tests/ExtensionTests.cs | 12 +- .../MqttPacketReaderTests.cs | 2 +- .../TestMqttCommunicationAdapter.cs | 3 +- .../PerformanceTest.cs | 98 +++--- Tests/MQTTnet.TestApp.NetCore/Program.cs | 3 +- 31 files changed, 605 insertions(+), 653 deletions(-) delete mode 100644 Source/MQTTnet/Serializer/ByteReader.cs delete mode 100644 Source/MQTTnet/Serializer/ByteWriter.cs rename Source/MQTTnet/Server/{MqttClientPendingMessagesQueue.cs => MqttClientPendingPacketsQueue.cs} (93%) create mode 100644 Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs rename Source/MQTTnet/Server/{GetOrCreateClientSessionResult.cs => PrepareClientSessionResult.cs} (76%) delete mode 100644 Tests/MQTTnet.Core.Tests/ByteReaderTests.cs delete mode 100644 Tests/MQTTnet.Core.Tests/ByteWriterTests.cs diff --git a/Build/MQTTnet.Extensions.ManagedClient.nuspec b/Build/MQTTnet.Extensions.ManagedClient.nuspec index e6815541..714ed9e1 100644 --- a/Build/MQTTnet.Extensions.ManagedClient.nuspec +++ b/Build/MQTTnet.Extensions.ManagedClient.nuspec @@ -48,6 +48,8 @@ - + + + \ No newline at end of file diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec index f8667c44..16a51c2f 100644 --- a/Build/MQTTnet.Extensions.Rpc.nuspec +++ b/Build/MQTTnet.Extensions.Rpc.nuspec @@ -48,6 +48,8 @@ - + + + \ No newline at end of file diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 25c31fb5..5d584377 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -69,6 +69,8 @@ - + + + \ No newline at end of file diff --git a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs index e026e7ba..43e5c8c4 100644 --- a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs @@ -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 ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken); } diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 4fc681ad..7db9a2a7 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -40,52 +40,84 @@ 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)); - } - - public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken) - { - ThrowIfDisposed(); - _logger.Verbose("Disconnecting [Timeout={0}]", timeout); - - return ExecuteAndWrapExceptionAsync(() => - Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, cancellationToken)); - } - - public Task SendPacketAsync(TimeSpan timeout, MqttBasePacket packet, CancellationToken cancellationToken) - { - return ExecuteAndWrapExceptionAsync(() => + try { - _logger.Verbose("TX >>> {0} [Timeout={1}]", packet, timeout); + _logger.Verbose("Connecting [Timeout={0}]", timeout); + + await Internal.TaskExtensions + .TimeoutAfterAsync(ct => _channel.ConnectAsync(ct), timeout, cancellationToken) + .ConfigureAwait(false); + } + catch (Exception exception) + { + if (IsWrappedException(exception)) + { + throw; + } + + WrapException(exception); + } + } + + public async Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + + try + { + _logger.Verbose("Disconnecting [Timeout={0}]", timeout); + + await Internal.TaskExtensions + .TimeoutAfterAsync(ct => _channel.DisconnectAsync(), timeout, cancellationToken) + .ConfigureAwait(false); + } + catch (Exception exception) + { + if (IsWrappedException(exception)) + { + throw; + } + + WrapException(exception); + } + } + + public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) + { + try + { + _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); + } + catch (Exception exception) + { + if (IsWrappedException(exception)) + { + throw; + } + + WrapException(exception); + } } public async Task 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,19 +126,30 @@ 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 ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken) @@ -138,7 +181,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(); @@ -155,42 +200,6 @@ namespace MQTTnet.Adapter } } - private static async Task ExecuteAndWrapExceptionAsync(Func 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 +214,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); + } } } diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 79a0ecff..e9362f6d 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -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,25 +249,23 @@ 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."); + } } } @@ -279,7 +277,7 @@ namespace MQTTnet.Client } _sendTracker.Restart(); - return _adapter.SendPacketAsync(_options.CommunicationTimeout, packet, cancellationToken); + return _adapter.SendPacketAsync(packet, cancellationToken); } private async Task SendAndReceiveAsync(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket @@ -300,8 +298,8 @@ namespace MQTTnet.Client var packetAwaiter = _packetDispatcher.AddPacketAwaiter(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; } diff --git a/Source/MQTTnet/Diagnostics/TargetFrameworkInfoProvider.cs b/Source/MQTTnet/Diagnostics/TargetFrameworkInfoProvider.cs index 52582769..efbf08b8 100644 --- a/Source/MQTTnet/Diagnostics/TargetFrameworkInfoProvider.cs +++ b/Source/MQTTnet/Diagnostics/TargetFrameworkInfoProvider.cs @@ -10,6 +10,8 @@ return "net452"; #elif NET461 return "net461"; +#elif NET472 + return "net472"; #elif NETSTANDARD1_3 return "netstandard1.3"; #elif NETSTANDARD2_0 diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 20829be1..c48a06f6 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -1,4 +1,4 @@ -#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0 +#if !WINDOWS_UWP using System; using System.Net.Security; using System.Net.Sockets; diff --git a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs index 8783e9cf..eeefb344 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs @@ -1,4 +1,4 @@ -#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0 +#if !WINDOWS_UWP using System; using System.Collections.Generic; using System.Net.Sockets; diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs index fccd77f3..b77d9b36 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs @@ -1,4 +1,4 @@ -#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0 +#if !WINDOWS_UWP using System; using System.Net; using System.Net.Security; @@ -76,7 +76,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, null), new MqttPacketSerializer(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); @@ -102,7 +103,7 @@ namespace MQTTnet.Implementations { _socket?.Dispose(); -#if NETSTANDARD1_3 || NETSTANDARD2_0 || NET461 +#if NETSTANDARD1_3 || NETSTANDARD2_0 || NET461 || NET472 _tlsCertificate?.Dispose(); #endif } diff --git a/Source/MQTTnet/Internal/TaskExtensions.cs b/Source/MQTTnet/Internal/TaskExtensions.cs index 288ac0ba..1356d97d 100644 --- a/Source/MQTTnet/Internal/TaskExtensions.cs +++ b/Source/MQTTnet/Internal/TaskExtensions.cs @@ -7,7 +7,7 @@ namespace MQTTnet.Internal { public static class TaskExtensions { - public static async Task TimeoutAfter(Func action, TimeSpan timeout, CancellationToken cancellationToken) + public static async Task TimeoutAfterAsync(Func action, TimeSpan timeout, CancellationToken cancellationToken) { if (action == null) throw new ArgumentNullException(nameof(action)); @@ -31,7 +31,7 @@ namespace MQTTnet.Internal } } - public static async Task TimeoutAfter(Func> action, TimeSpan timeout, CancellationToken cancellationToken) + public static async Task TimeoutAfterAsync(Func> action, TimeSpan timeout, CancellationToken cancellationToken) { if (action == null) throw new ArgumentNullException(nameof(action)); diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 83643782..4afd09d0 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -1,7 +1,7 @@  - netstandard1.3;netstandard2.0;net452;net461;uap10.0 + netstandard1.3;netstandard2.0;net452;uap10.0 netstandard1.3;netstandard2.0 MQTTnet MQTTnet @@ -62,7 +62,4 @@ - - - \ No newline at end of file diff --git a/Source/MQTTnet/Serializer/ByteReader.cs b/Source/MQTTnet/Serializer/ByteReader.cs deleted file mode 100644 index 461bba33..00000000 --- a/Source/MQTTnet/Serializer/ByteReader.cs +++ /dev/null @@ -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; - } - } -} diff --git a/Source/MQTTnet/Serializer/ByteWriter.cs b/Source/MQTTnet/Serializer/ByteWriter.cs deleted file mode 100644 index 9ae21564..00000000 --- a/Source/MQTTnet/Serializer/ByteWriter.cs +++ /dev/null @@ -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++; - } - } -} diff --git a/Source/MQTTnet/Serializer/MqttPacketReader.cs b/Source/MQTTnet/Serializer/MqttPacketReader.cs index 7ed918f6..d50fae5d 100644 --- a/Source/MQTTnet/Serializer/MqttPacketReader.cs +++ b/Source/MQTTnet/Serializer/MqttPacketReader.cs @@ -12,6 +12,8 @@ namespace MQTTnet.Serializer { // 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. + // 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 = new byte[2]; var totalBytesRead = 0; @@ -37,11 +39,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 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 +52,11 @@ namespace MQTTnet.Serializer while ((encodedByte & 128) != 0) { - encodedByte = await ReadByteAsync(channel, cancellationToken).ConfigureAwait(false); + // 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 = ReadByteAsync(channel, cancellationToken).GetAwaiter().GetResult(); value += (byte)(encodedByte & 127) * multiplier; if (multiplier > 128 * 128 * 128) diff --git a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs b/Source/MQTTnet/Serializer/MqttPacketSerializer.cs index 2acbfbbc..811acf70 100644 --- a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttPacketSerializer.cs @@ -2,7 +2,6 @@ using MQTTnet.Packets; using MQTTnet.Protocol; using System; -using System.IO; using System.Linq; using MQTTnet.Adapter; @@ -18,57 +17,46 @@ namespace MQTTnet.Serializer { 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); + var packetWriter = new MqttPacketWriter(); - var fixedHeader = SerializePacket(packet, stream); - var remainingLength = (int)stream.Length - 5; + // Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes) + packetWriter.Seek(5); - var remainingLengthBuffer = MqttPacketWriter.EncodeRemainingLength(remainingLength); + var fixedHeader = SerializePacket(packet, packetWriter); + var remainingLength = packetWriter.Length - 5; - var headerSize = FixedHeaderSize + remainingLengthBuffer.Count; - var headerOffset = 5 - headerSize; + var remainingLengthBuffer = MqttPacketWriter.EncodeRemainingLength(remainingLength); - // 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); + var headerSize = FixedHeaderSize + remainingLengthBuffer.Count; + var headerOffset = 5 - headerSize; -#if NET461 || NET452 || NETSTANDARD2_0 - var buffer = stream.GetBuffer(); - return new ArraySegment(buffer, headerOffset, (int)stream.Length - headerOffset); -#else - if (stream.TryGetBuffer(out var segment)) - { - return new ArraySegment(segment.Array, headerOffset, segment.Count - headerOffset); - } + // 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); - var buffer = stream.ToArray(); - return new ArraySegment(buffer, headerOffset, buffer.Length - headerOffset); -#endif - } + var buffer = packetWriter.GetBuffer(); + return new ArraySegment(buffer, headerOffset, packetWriter.Length - headerOffset); } - private byte SerializePacket(MqttBasePacket packet, Stream stream) + private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter packetWriter) { switch (packet) { - case MqttConnectPacket connectPacket: return Serialize(connectPacket, stream); - case MqttConnAckPacket connAckPacket: return Serialize(connAckPacket, stream); + 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, 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); + 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."); } } @@ -195,10 +183,9 @@ namespace MQTTnet.Serializer var body = receivedMqttPacket.Body; ThrowIfBodyIsEmpty(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(); @@ -253,8 +240,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 +249,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 +309,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,119 +331,129 @@ 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); + byte connectFlags = 0x0; + if (packet.CleanSession) + { + connectFlags |= 0x2; + } if (packet.WillMessage != null) { - connectFlags.Write((int)packet.WillMessage.QualityOfServiceLevel, 2); - connectFlags.Write(packet.WillMessage.Retain); - } - else - { - 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) { 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); - - stream.Write(connectFlags); - stream.Write(packet.KeepAlivePeriod); - stream.WriteWithLengthPrefix(packet.ClientId); - - if (packet.WillMessage != null) + if (packet.Password != null) { - stream.WriteWithLengthPrefix(packet.WillMessage.Topic); - stream.WriteWithLengthPrefix(packet.WillMessage.Payload); + connectFlags |= 0x40; } if (packet.Username != null) { - stream.WriteWithLengthPrefix(packet.Username); + connectFlags |= 0x80; + } + + packetWriter.Write(connectFlags); + packetWriter.Write(packet.KeepAlivePeriod); + packetWriter.WriteWithLengthPrefix(packet.ClientId); + + if (packet.WillMessage != null) + { + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Topic); + packetWriter.WriteWithLengthPrefix(packet.WillMessage.Payload); + } + + if (packet.Username != null) + { + packetWriter.WriteWithLengthPrefix(packet.Username); } if (packet.Password != null) { - 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); - - stream.Write(connectAcknowledgeFlags); + byte connectAcknowledgeFlags = 0x0; + if (packet.IsSessionPresent) + { + connectAcknowledgeFlags |= 0x1; + } + + 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 +462,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 +474,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 +494,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 +539,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 +582,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 +611,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) diff --git a/Source/MQTTnet/Serializer/MqttPacketWriter.cs b/Source/MQTTnet/Serializer/MqttPacketWriter.cs index 2cc9d7bb..c0c49fcb 100644 --- a/Source/MQTTnet/Serializer/MqttPacketWriter.cs +++ b/Source/MQTTnet/Serializer/MqttPacketWriter.cs @@ -1,12 +1,23 @@ using System; -using System.IO; using System.Text; using MQTTnet.Protocol; namespace MQTTnet.Serializer { - public static class MqttPacketWriter + /// + /// 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). + /// + public class MqttPacketWriter { + private byte[] _buffer = new byte[128]; + + private int _position; + + public int Length { get; private set; } + public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0) { var fixedHeader = (int)packetType << 4; @@ -14,33 +25,6 @@ namespace MQTTnet.Serializer return (byte)fixedHeader; } - public static void Write(this Stream stream, ushort value) - { - var buffer = BitConverter.GetBytes(value); - stream.WriteByte(buffer[1]); - stream.WriteByte(buffer[0]); - } - - public static void Write(this Stream stream, ByteWriter value) - { - if (value == null) throw new ArgumentNullException(nameof(value)); - - stream.WriteByte(value.Value); - } - - public static void WriteWithLengthPrefix(this Stream stream, string value) - { - stream.WriteWithLengthPrefix(Encoding.UTF8.GetBytes(value ?? string.Empty)); - } - - public static void WriteWithLengthPrefix(this Stream stream, byte[] value) - { - var length = (ushort)value.Length; - - stream.Write(length); - stream.Write(value, 0, length); - } - public static ArraySegment EncodeRemainingLength(int length) { // write the encoded remaining length right aligned on the 4 byte buffer @@ -69,5 +53,91 @@ namespace MQTTnet.Serializer return new ArraySegment(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 Seek(int offset) + { + EnsureCapacity(offset); + _position = offset; + } + + public byte[] GetBuffer() + { + return _buffer; + } + + 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; + } + } } } diff --git a/Source/MQTTnet/Server/MqttClientPendingMessagesQueue.cs b/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs similarity index 93% rename from Source/MQTTnet/Server/MqttClientPendingMessagesQueue.cs rename to Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs index ff43e6fc..7a55c0b2 100644 --- a/Source/MQTTnet/Server/MqttClientPendingMessagesQueue.cs +++ b/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs @@ -11,7 +11,7 @@ using MQTTnet.Protocol; namespace MQTTnet.Server { - public class MqttClientPendingMessagesQueue : IDisposable + public class MqttClientPendingPacketsQueue : IDisposable { private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent(); private readonly IMqttServerOptions _options; @@ -20,13 +20,13 @@ namespace MQTTnet.Server private ConcurrentQueue _queue = new ConcurrentQueue(); - 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; @@ -115,7 +115,7 @@ namespace MQTTnet.Server return; } - await adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, packet, cancellationToken).ConfigureAwait(false); + await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); _logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); } diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index 28e97f02..460bea49 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -18,7 +18,7 @@ namespace MQTTnet.Server private readonly MqttRetainedMessagesManager _retainedMessagesManager; private readonly MqttClientKeepAliveMonitor _keepAliveMonitor; - private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue; + private readonly MqttClientPendingPacketsQueue _pendingPacketsQueue; private readonly MqttClientSubscriptionsManager _subscriptionsManager; private readonly MqttClientSessionsManager _sessionsManager; @@ -49,7 +49,7 @@ namespace MQTTnet.Server _keepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, () => Stop(MqttClientDisconnectType.NotClean), _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) @@ -149,13 +149,10 @@ namespace MQTTnet.Server if (_willMessage != null && !_wasCleanDisconnect) { - _sessionsManager.StartDispatchApplicationMessage(this, _willMessage); + _sessionsManager.EnqueueApplicationMessage(this, _willMessage); } _willMessage = null; - - ////_pendingMessagesQueue.WaitForCompletion(); - ////_keepAliveMonitor.WaitForCompletion(); } finally { @@ -196,7 +193,7 @@ namespace MQTTnet.Server } } - _pendingMessagesQueue.Enqueue(publishPacket); + _pendingPacketsQueue.Enqueue(publishPacket); } public Task SubscribeAsync(IList topicFilters) @@ -226,12 +223,12 @@ namespace MQTTnet.Server public void ClearPendingApplicationMessages() { - _pendingMessagesQueue.Clear(); + _pendingPacketsQueue.Clear(); } public void Dispose() { - _pendingMessagesQueue?.Dispose(); + _pendingPacketsQueue?.Dispose(); _cancellationTokenSource?.Dispose(); } @@ -245,7 +242,7 @@ namespace MQTTnet.Server if (packet is MqttPingReqPacket) { - return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, new MqttPingRespPacket(), cancellationToken); + return adapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken); } if (packet is MqttPubRelPacket pubRelPacket) @@ -260,7 +257,7 @@ namespace MQTTnet.Server PacketIdentifier = pubRecPacket.PacketIdentifier }; - return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, responsePacket, cancellationToken); + return adapter.SendPacketAsync(responsePacket, cancellationToken); } if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) @@ -308,7 +305,7 @@ namespace MQTTnet.Server private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) { var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket); - await adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false); + await adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false); if (subscribeResult.CloseConnection) { @@ -322,7 +319,7 @@ namespace MQTTnet.Server private Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) { var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket); - return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, unsubscribeResult, cancellationToken); + return adapter.SendPacketAsync(unsubscribeResult, cancellationToken); } private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) @@ -333,7 +330,7 @@ namespace MQTTnet.Server { case MqttQualityOfServiceLevel.AtMostOnce: { - _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage); + _sessionsManager.EnqueueApplicationMessage(this, applicationMessage); return Task.FromResult(0); } case MqttQualityOfServiceLevel.AtLeastOnce: @@ -353,25 +350,25 @@ namespace MQTTnet.Server private Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) { - _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage); + _sessionsManager.EnqueueApplicationMessage(this, applicationMessage); var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }; - return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken); + return adapter.SendPacketAsync(response, cancellationToken); } private Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, 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, applicationMessage); var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; - return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken); + return adapter.SendPacketAsync(response, cancellationToken); } - private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) + private static Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) { var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }; - return adapter.SendPacketAsync(_options.DefaultCommunicationTimeout, response, cancellationToken); + return adapter.SendPacketAsync(response, cancellationToken); } private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index c52c706c..1cb301ab 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -6,27 +6,29 @@ using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; -using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; namespace MQTTnet.Server { - public class MqttClientSessionsManager : IDisposable + public class MqttClientSessionsManager { + private readonly BlockingCollection _messageQueue = new BlockingCollection(); private readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); - private readonly AsyncLock _sessionPreparationLock = new AsyncLock(); + + 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,83 +36,9 @@ namespace MQTTnet.Server public MqttServer Server { get; } - public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) + public void Start() { - var clientId = string.Empty; - var wasCleanDisconnect = false; - - try - { - var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); - if (firstPacket == null) - { - return; - } - - if (!(firstPacket is MqttConnectPacket connectPacket)) - { - throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); - } - - clientId = connectPacket.ClientId; - - // Switch to the required protocol version before sending any response. - clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion; - - var connectReturnCode = ValidateConnection(connectPacket); - if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) - { - await clientAdapter.SendPacketAsync(_options.DefaultCommunicationTimeout, - new MqttConnAckPacket - { - ConnectReturnCode = connectReturnCode - }, - cancellationToken).ConfigureAwait(false); - - return; - } - - var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false); - var clientSession = result.Session; - - await clientAdapter.SendPacketAsync(_options.DefaultCommunicationTimeout, - new MqttConnAckPacket - { - ConnectReturnCode = connectReturnCode, - IsSessionPresent = result.IsExistingSession - }, - cancellationToken).ConfigureAwait(false); - - Server.OnClientConnected(clientId); - - wasCleanDisconnect = await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } - catch (Exception exception) - { - _logger.Error(exception, exception.Message); - } - finally - { - try - { - await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false); - clientAdapter.Dispose(); - } - catch (Exception exception) - { - _logger.Error(exception, exception.Message); - } - - if (!_options.EnablePersistentSessions) - { - DeleteSession(clientId); - } - - Server.OnClientDisconnected(clientId, wasCleanDisconnect); - } + Task.Factory.StartNew(() => ProcessQueuedApplicationMessages(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); } public Task StopAsync() @@ -124,6 +52,11 @@ namespace MQTTnet.Server return Task.FromResult(0); } + public Task StartSession(IMqttChannelAdapter clientAdapter) + { + return Task.Run(() => RunSession(clientAdapter, _cancellationToken), _cancellationToken); + } + public Task> GetClientStatusAsync() { var result = new List(); @@ -138,9 +71,11 @@ namespace MQTTnet.Server return Task.FromResult((IList)result); } - public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) + public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) { - Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage)); + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + + _messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, applicationMessage), _cancellationToken); } public Task SubscribeAsync(string clientId, IList topicFilters) @@ -175,9 +110,131 @@ namespace MQTTnet.Server _logger.Verbose("Session for client '{0}' deleted.", clientId); } - public void Dispose() + private void ProcessQueuedApplicationMessages(CancellationToken cancellationToken) { - _sessionPreparationLock?.Dispose(); + while (!cancellationToken.IsCancellationRequested) + { + try + { + var enqueuedApplicationMessage = _messageQueue.Take(cancellationToken); + var sender = enqueuedApplicationMessage.Sender; + var applicationMessage = enqueuedApplicationMessage.ApplicationMessage; + + 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 _sessions.Values) + { + clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage); + } + } + catch (TaskCanceledException) + { + } + catch (Exception exception) + { + _logger.Error(exception, "Unhandled exception while processing queued application message."); + } + } + } + + private async Task RunSession(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) + { + var clientId = string.Empty; + var wasCleanDisconnect = false; + + try + { + var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); + if (firstPacket == null) + { + return; + } + + if (!(firstPacket is MqttConnectPacket connectPacket)) + { + throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); + } + + clientId = connectPacket.ClientId; + + // Switch to the required protocol version before sending any response. + clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion; + + var connectReturnCode = ValidateConnection(connectPacket); + if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) + { + await clientAdapter.SendPacketAsync( + new MqttConnAckPacket + { + ConnectReturnCode = connectReturnCode + }, + cancellationToken).ConfigureAwait(false); + + return; + } + + var result = PrepareClientSession(connectPacket); + var clientSession = result.Session; + + await clientAdapter.SendPacketAsync( + new MqttConnAckPacket + { + ConnectReturnCode = connectReturnCode, + IsSessionPresent = result.IsExistingSession + }, + cancellationToken).ConfigureAwait(false); + + Server.OnClientConnected(clientId); + + wasCleanDisconnect = await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } + catch (Exception exception) + { + _logger.Error(exception, exception.Message); + } + finally + { + try + { + await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false); + clientAdapter.Dispose(); + } + catch (Exception exception) + { + _logger.Error(exception, exception.Message); + } + + if (!_options.EnablePersistentSessions) + { + DeleteSession(clientId); + } + + Server.OnClientDisconnected(clientId, wasCleanDisconnect); + } } private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) @@ -197,9 +254,9 @@ namespace MQTTnet.Server return context.ReturnCode; } - private async Task 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) @@ -231,60 +288,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; } diff --git a/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs b/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs new file mode 100644 index 00000000..20ff2feb --- /dev/null +++ b/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs @@ -0,0 +1,15 @@ +namespace MQTTnet.Server +{ + public class MqttEnqueuedApplicationMessage + { + public MqttEnqueuedApplicationMessage(MqttClientSession sender, MqttApplicationMessage applicationMessage) + { + Sender = sender; + ApplicationMessage = applicationMessage; + } + + public MqttClientSession Sender { get; } + + public MqttApplicationMessage ApplicationMessage { get; } + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index a38dfd21..38e631e9 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -65,7 +65,7 @@ namespace MQTTnet.Server if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started."); - _clientSessionsManager.StartDispatchApplicationMessage(null, applicationMessage); + _clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage); return Task.FromResult(0); } @@ -81,7 +81,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) { @@ -118,8 +119,6 @@ namespace MQTTnet.Server } finally { - _clientSessionsManager?.Dispose(); - _cancellationTokenSource = null; _retainedMessagesManager = null; _clientSessionsManager = null; @@ -155,9 +154,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); } } } diff --git a/Source/MQTTnet/Server/GetOrCreateClientSessionResult.cs b/Source/MQTTnet/Server/PrepareClientSessionResult.cs similarity index 76% rename from Source/MQTTnet/Server/GetOrCreateClientSessionResult.cs rename to Source/MQTTnet/Server/PrepareClientSessionResult.cs index 975d237f..9a655bec 100644 --- a/Source/MQTTnet/Server/GetOrCreateClientSessionResult.cs +++ b/Source/MQTTnet/Server/PrepareClientSessionResult.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Server { - public class GetOrCreateClientSessionResult + public class PrepareClientSessionResult { public bool IsExistingSession { get; set; } diff --git a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs index 20b581c0..7f068d60 100644 --- a/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -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; diff --git a/Tests/MQTTnet.Core.Tests/ByteReaderTests.cs b/Tests/MQTTnet.Core.Tests/ByteReaderTests.cs deleted file mode 100644 index e2173cbc..00000000 --- a/Tests/MQTTnet.Core.Tests/ByteReaderTests.cs +++ /dev/null @@ -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)); - } - } -} diff --git a/Tests/MQTTnet.Core.Tests/ByteWriterTests.cs b/Tests/MQTTnet.Core.Tests/ByteWriterTests.cs deleted file mode 100644 index 881df5c5..00000000 --- a/Tests/MQTTnet.Core.Tests/ByteWriterTests.cs +++ /dev/null @@ -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); - } - } -} diff --git a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs index 696cfa3c..fdb5888e 100644 --- a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs +++ b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs @@ -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); diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs index e3e1ccd3..f983622b 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs @@ -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(); diff --git a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs index ffe2a77a..9375f07d 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs @@ -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(); diff --git a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index e9e4bc46..752feb58 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -12,16 +12,46 @@ namespace MQTTnet.TestApp.NetCore { public static class PerformanceTest { - public static async Task RunAsync() + public static void Run() { - Console.WriteLine("Press 'c' for concurrent sends. Otherwise in one batch."); - var concurrent = Console.ReadKey(true).KeyChar == 'c'; + try + { + var mqttServer = new MqttFactory().CreateMqttServer(); + mqttServer.StartAsync(new MqttServerOptions()).GetAwaiter().GetResult(); - var server = Task.Run(RunServerAsync); - await Task.Delay(1000); - var client = Task.Run(() => RunClientAsync(2000, TimeSpan.FromMilliseconds(10), concurrent)); + var options = new MqttClientOptions + { + ChannelOptions = new MqttClientTcpOptions + { + Server = "127.0.0.1" + }, + CleanSession = true + }; - await Task.WhenAll(server, client).ConfigureAwait(false); + var client = new MqttFactory().CreateMqttClient(); + client.ConnectAsync(options).GetAwaiter().GetResult(); + + var message = CreateMessage(); + var stopwatch = new Stopwatch(); + + for (var i = 0; i < 10; i++) + { + stopwatch.Restart(); + + var sentMessagesCount = 0; + while (stopwatch.ElapsedMilliseconds < 1000) + { + client.PublishAsync(message).GetAwaiter().GetResult(); + sentMessagesCount++; + } + + Console.WriteLine($"Sending {sentMessagesCount} messages per second. #" + (i + 1)); + } + } + catch (Exception exception) + { + Console.WriteLine(exception); + } } private static Task RunClientsAsync(int msgChunkSize, TimeSpan interval, bool concurrent) @@ -53,29 +83,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 +151,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(); - } } } diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index f8a5d275..25302c7b 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -34,7 +34,8 @@ namespace MQTTnet.TestApp.NetCore } else if (pressedKey.KeyChar == '3') { - Task.Run(PerformanceTest.RunAsync); + PerformanceTest.Run(); + return; } else if (pressedKey.KeyChar == '4') { From c758ae89f4bdff2a8b6b91412e7d5cbd80b56755 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sun, 27 May 2018 08:24:15 +0200 Subject: [PATCH 102/138] first iteration --- .../ConnectionBuilderExtensions.cs | 12 ++ .../MqttConnectionContext.cs | 136 ++++++++++++++++++ .../MqttConnectionHandler.cs | 40 ++++++ .../MQTTnet.AspnetCore/ReaderExtensions.cs | 115 +++++++++++++++ .../MQTTnet.AspNetCore.csproj | 9 +- .../ServiceCollectionExtensions.cs | 3 +- Tests/MQTTnet.TestApp.AspNetCore2/Program.cs | 2 + 7 files changed, 313 insertions(+), 4 deletions(-) create mode 100644 Frameworks/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs create mode 100644 Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs create mode 100644 Frameworks/MQTTnet.AspnetCore/MqttConnectionHandler.cs create mode 100644 Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs diff --git a/Frameworks/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs new file mode 100644 index 00000000..262a333f --- /dev/null +++ b/Frameworks/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs @@ -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(); + } + } +} diff --git a/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs new file mode 100644 index 00000000..0bd2b4e3 --- /dev/null +++ b/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -0,0 +1,136 @@ +using Microsoft.AspNetCore.Connections; +using MQTTnet.Adapter; +using MQTTnet.Packets; +using MQTTnet.Serializer; +using System; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; + +namespace MQTTnet.AspNetCore +{ + public class MqttConnectionContext : IMqttChannelAdapter + { + public IMqttPacketSerializer PacketSerializer { get; } + public ConnectionContext Connection { get; } + + public string Endpoint => Connection.ConnectionId; + + public MqttConnectionContext( + IMqttPacketSerializer packetSerializer, + ConnectionContext connection) + { + PacketSerializer = packetSerializer; + Connection = connection; + } + + public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken) + { + Connection.Transport.Input.Complete(); + Connection.Transport.Output.Complete(); + return Task.CompletedTask; + } + + public void Dispose() + { + } + + public async Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) + { + var input = Connection.Transport.Input; + + 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 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); + } + } + + cancellationToken.ThrowIfCancellationRequested(); + return null; + } + + public async Task SendPacketsAsync(TimeSpan timeout, IEnumerable packets, CancellationToken cancellationToken) + { + foreach (var packet in packets) + { + await WriteAsync(packet); + } + } + + public async Task WriteAsync(MqttBasePacket packet) + { + var buffer = PacketSerializer.Serialize(packet); + await Connection.Transport.Output.WriteAsync(buffer.AsMemory()); + } + + private int messageId; + public Task PublishAsync(MqttPublishPacket packet) + { + if (!packet.PacketIdentifier.HasValue && packet.QualityOfServiceLevel > MQTTnet.Protocol.MqttQualityOfServiceLevel.AtMostOnce) + { + packet.PacketIdentifier = (ushort)Interlocked.Increment(ref messageId); + } + return WriteAsync(packet); + } + + public Task SubscribeAsync(MqttSubscribePacket packet) + { + if (!packet.PacketIdentifier.HasValue) + { + packet.PacketIdentifier = (ushort)Interlocked.Increment(ref messageId); + } + return WriteAsync(packet); + } + + public Task ConnectAsync(MqttConnectPacket packet) + { + if (string.IsNullOrEmpty(packet.ClientId)) + { + packet.ClientId = Guid.NewGuid().ToString(); + } + return WriteAsync(packet); + } + } +} diff --git a/Frameworks/MQTTnet.AspnetCore/MqttConnectionHandler.cs b/Frameworks/MQTTnet.AspnetCore/MqttConnectionHandler.cs new file mode 100644 index 00000000..49a5c098 --- /dev/null +++ b/Frameworks/MQTTnet.AspnetCore/MqttConnectionHandler.cs @@ -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 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() + { + } + } +} diff --git a/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs new file mode 100644 index 00000000..a4e5e021 --- /dev/null +++ b/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs @@ -0,0 +1,115 @@ +using System; +using System.Buffers; +using System.IO; +using MQTTnet.Exceptions; +using MQTTnet.Packets; +using MQTTnet.Protocol; +using MQTTnet.Serializer; + +namespace MQTTnet.AspNetCore +{ + public static class ReaderExtensions + { + public static MqttPacketHeader ReadHeader(this ref ReadOnlySequence input) + { + if (input.Length < 2) + { + return null; + } + + var fixedHeader = input.First.Span[0]; + var controlPacketType = (MqttControlPacketType)(fixedHeader >> 4); + var bodyLength = ReadBodyLength(ref input); + + return new MqttPacketHeader + { + FixedHeader = fixedHeader, + ControlPacketType = controlPacketType, + BodyLength = bodyLength + }; + } + + private static int ReadBodyLength(ref ReadOnlySequence input) + { + // 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; + + var temp = input.Slice(0, Math.Min(5, input.Length)).GetArray(); + + do + { + 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); + + return value; + } + + + + public static byte[] GetArray(this in ReadOnlySequence input) + { + if (input.IsSingleSegment) + { + return input.First.Span.ToArray(); + } + + // Should be rare + return input.ToArray(); + } + + public static bool TryDeserialize(this IMqttPacketSerializer serializer, ref ReadOnlySequence input, out MqttBasePacket packet) + { + packet = null; + var copy = input; + var header = copy.ReadHeader(); + if (header == null || copy.Length < header.BodyLength) + { + return false; + } + + input = copy.Slice(header.BodyLength); + var bodySlice = copy.Slice(0, header.BodyLength); + using (var body = new MemoryStream(bodySlice.GetArray())) + { + packet = serializer.Deserialize(header, body); + return true; + } + } + + public static bool TryDeserialize(this IMqttPacketSerializer serializer, in ReadOnlySequence input, out MqttBasePacket packet, out SequencePosition consumed, out SequencePosition observed) + { + packet = null; + var copy = input; + var header = copy.ReadHeader(); + if (header == null || copy.Length < header.BodyLength) + { + consumed = input.Start; + observed = input.End; + return false; + } + + var bodySlice = copy.Slice(0, header.BodyLength); + using (var body = new MemoryStream(bodySlice.GetArray())) + { + packet = serializer.Deserialize(header, body); + consumed = bodySlice.End; + observed = bodySlice.End; + return true; + } + } + } +} diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj index 6b858954..661ff168 100644 --- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj +++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj @@ -9,6 +9,7 @@ + 7.2 @@ -16,9 +17,11 @@ - - - + + + + + diff --git a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs index 5b8f1612..9d2a6762 100644 --- a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs @@ -30,7 +30,8 @@ namespace MQTTnet.AspNetCore if (options.DefaultEndpointOptions.IsEnabled) { - services.AddSingleton(s => s.GetService()); + services.AddSingleton(); + services.AddSingleton(s => s.GetService()); } return services; diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs index 38b8c12d..a7164117 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; +using MQTTnet.AspNetCore; namespace MQTTnet.TestApp.AspNetCore2 { @@ -12,6 +13,7 @@ namespace MQTTnet.TestApp.AspNetCore2 private static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) + .UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt())) .UseStartup() .Build(); } From 7a7fab090756d4390caf38c2bc6168edbdbf6d14 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Fri, 1 Jun 2018 21:13:55 +0200 Subject: [PATCH 103/138] fixed rebase --- .../MqttConnectionContext.cs | 8 ++- .../MQTTnet.AspnetCore/ReaderExtensions.cs | 62 +++++++++---------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs index 0bd2b4e3..7683fd01 100644 --- a/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -48,7 +48,8 @@ namespace MQTTnet.AspNetCore while (!cancellationToken.IsCancellationRequested) { ReadResult readResult; - + ReadingPacketStarted?.Invoke(this, EventArgs.Empty); + var readTask = input.ReadAsync(cancellationToken); if (readTask.IsCompleted) { @@ -84,6 +85,7 @@ namespace MQTTnet.AspNetCore // 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); + ReadingPacketCompleted?.Invoke(this, EventArgs.Empty); } } @@ -106,6 +108,10 @@ namespace MQTTnet.AspNetCore } private int messageId; + + public event EventHandler ReadingPacketStarted; + public event EventHandler ReadingPacketCompleted; + public Task PublishAsync(MqttPublishPacket packet) { if (!packet.PacketIdentifier.HasValue && packet.QualityOfServiceLevel > MQTTnet.Protocol.MqttQualityOfServiceLevel.AtMostOnce) diff --git a/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs index a4e5e021..718538b1 100644 --- a/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs +++ b/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Buffers; using System.IO; +using MQTTnet.Adapter; using MQTTnet.Exceptions; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -10,37 +11,23 @@ namespace MQTTnet.AspNetCore { public static class ReaderExtensions { - public static MqttPacketHeader ReadHeader(this ref ReadOnlySequence input) - { - if (input.Length < 2) - { - return null; - } - - var fixedHeader = input.First.Span[0]; - var controlPacketType = (MqttControlPacketType)(fixedHeader >> 4); - var bodyLength = ReadBodyLength(ref input); - - return new MqttPacketHeader - { - FixedHeader = fixedHeader, - ControlPacketType = controlPacketType, - BodyLength = bodyLength - }; - } - - private static int ReadBodyLength(ref ReadOnlySequence input) + private static bool TryReadBodyLength(ref ReadOnlySequence 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++; @@ -55,7 +42,8 @@ namespace MQTTnet.AspNetCore input = input.Slice(index); - return value; + result = value; + return true; } @@ -75,17 +63,22 @@ namespace MQTTnet.AspNetCore { packet = null; var copy = input; - var header = copy.ReadHeader(); - if (header == null || copy.Length < header.BodyLength) + if (copy.Length < 2) { return false; } - input = copy.Slice(header.BodyLength); - var bodySlice = copy.Slice(0, header.BodyLength); + var fixedheader = copy.First.Span[0]; + if (!TryReadBodyLength(ref copy, out var bodyLength)) + { + return false; + } + + input = copy.Slice(bodyLength); + var bodySlice = copy.Slice(0, bodyLength); using (var body = new MemoryStream(bodySlice.GetArray())) { - packet = serializer.Deserialize(header, body); + packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, body)); return true; } } @@ -93,19 +86,24 @@ namespace MQTTnet.AspNetCore public static bool TryDeserialize(this IMqttPacketSerializer serializer, in ReadOnlySequence input, out MqttBasePacket packet, out SequencePosition consumed, out SequencePosition observed) { packet = null; + consumed = input.Start; + observed = input.End; var copy = input; - var header = copy.ReadHeader(); - if (header == null || copy.Length < header.BodyLength) + if (copy.Length < 2) { - consumed = input.Start; - observed = input.End; return false; } - var bodySlice = copy.Slice(0, header.BodyLength); + var fixedheader = copy.First.Span[0]; + if (!TryReadBodyLength(ref copy, out var bodyLength)) + { + return false; + } + + var bodySlice = copy.Slice(0, bodyLength); using (var body = new MemoryStream(bodySlice.GetArray())) { - packet = serializer.Deserialize(header, body); + packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, body)); consumed = bodySlice.End; observed = bodySlice.End; return true; From d5ac8c7183ff3af9313fc441afe858b3890012d9 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Fri, 1 Jun 2018 21:41:04 +0200 Subject: [PATCH 104/138] use final packages --- Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj | 8 +++----- .../MQTTnet.TestApp.AspNetCore2.csproj | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj index 661ff168..9754f40c 100644 --- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj +++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj @@ -17,11 +17,9 @@ - - - - - + + + diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj index c587ecc8..f6ead870 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj @@ -1,7 +1,7 @@  - netcoreapp2.0 + netcoreapp2.1 Latest @@ -10,7 +10,7 @@ - + From 7c2adf636fd26d6cbc4e32b331d93c4c285d7826 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 2 Jun 2018 08:35:41 +0200 Subject: [PATCH 105/138] removed dead code and make new connection context an opt in so kestrel 2.0 still works --- .../MqttConnectionContext.cs | 40 ++----------------- .../MQTTnet.AspnetCore/ReaderExtensions.cs | 25 ------------ .../ServiceCollectionExtensions.cs | 12 +++++- Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs | 8 +++- 4 files changed, 19 insertions(+), 66 deletions(-) diff --git a/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs index 7683fd01..88bfcc91 100644 --- a/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -97,46 +97,12 @@ namespace MQTTnet.AspNetCore { foreach (var packet in packets) { - await WriteAsync(packet); + var buffer = PacketSerializer.Serialize(packet); + await Connection.Transport.Output.WriteAsync(buffer.AsMemory()); } } - - public async Task WriteAsync(MqttBasePacket packet) - { - var buffer = PacketSerializer.Serialize(packet); - await Connection.Transport.Output.WriteAsync(buffer.AsMemory()); - } - - private int messageId; - + public event EventHandler ReadingPacketStarted; public event EventHandler ReadingPacketCompleted; - - public Task PublishAsync(MqttPublishPacket packet) - { - if (!packet.PacketIdentifier.HasValue && packet.QualityOfServiceLevel > MQTTnet.Protocol.MqttQualityOfServiceLevel.AtMostOnce) - { - packet.PacketIdentifier = (ushort)Interlocked.Increment(ref messageId); - } - return WriteAsync(packet); - } - - public Task SubscribeAsync(MqttSubscribePacket packet) - { - if (!packet.PacketIdentifier.HasValue) - { - packet.PacketIdentifier = (ushort)Interlocked.Increment(ref messageId); - } - return WriteAsync(packet); - } - - public Task ConnectAsync(MqttConnectPacket packet) - { - if (string.IsNullOrEmpty(packet.ClientId)) - { - packet.ClientId = Guid.NewGuid().ToString(); - } - return WriteAsync(packet); - } } } diff --git a/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs index 718538b1..5b832539 100644 --- a/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs +++ b/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs @@ -4,7 +4,6 @@ using System.IO; using MQTTnet.Adapter; using MQTTnet.Exceptions; using MQTTnet.Packets; -using MQTTnet.Protocol; using MQTTnet.Serializer; namespace MQTTnet.AspNetCore @@ -59,30 +58,6 @@ namespace MQTTnet.AspNetCore return input.ToArray(); } - public static bool TryDeserialize(this IMqttPacketSerializer serializer, ref ReadOnlySequence input, out MqttBasePacket packet) - { - packet = null; - var copy = input; - if (copy.Length < 2) - { - return false; - } - - var fixedheader = copy.First.Span[0]; - if (!TryReadBodyLength(ref copy, out var bodyLength)) - { - return false; - } - - input = copy.Slice(bodyLength); - var bodySlice = copy.Slice(0, bodyLength); - using (var body = new MemoryStream(bodySlice.GetArray())) - { - packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, body)); - return true; - } - } - public static bool TryDeserialize(this IMqttPacketSerializer serializer, in ReadOnlySequence input, out MqttBasePacket packet, out SequencePosition consumed, out SequencePosition observed) { packet = null; diff --git a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs index 9d2a6762..fa061eb7 100644 --- a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs @@ -30,11 +30,19 @@ namespace MQTTnet.AspNetCore if (options.DefaultEndpointOptions.IsEnabled) { - services.AddSingleton(); - services.AddSingleton(s => s.GetService()); + services.AddSingleton(); + services.AddSingleton(s => s.GetService()); } return services; } + + public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(s => s.GetService()); + + return services; + } } } diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs index 591cc87d..f52040a0 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs @@ -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. From 78c605966883cba51fa594e7cb5500beec0b5897 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 2 Jun 2018 09:31:27 +0200 Subject: [PATCH 106/138] enabled clients based on tcp to benefit from new api and added Benchmark for EndtoEnd with new API --- .../MqttClientConnectionContextFactory.cs | 34 +++++++++ .../Client}/Tcp/BufferExtensions.cs | 2 +- .../Client}/Tcp/DuplexPipe.cs | 2 +- .../Client}/Tcp/SocketAwaitable.cs | 2 +- .../Client}/Tcp/SocketReceiver.cs | 2 +- .../Client}/Tcp/SocketSender.cs | 2 +- .../Client}/Tcp/TcpConnection.cs | 45 +++++++---- .../MqttConnectionContext.cs | 6 ++ .../MQTTnet.AspnetCore/ReaderExtensions.cs | 11 +-- Source/MQTTnet/MqttFactory.cs | 8 ++ .../MQTTnet.Benchmarks.csproj | 2 + ...rocessingMqttConnectionContextBenchmark.cs | 76 +++++++++++++++++++ Tests/MQTTnet.Benchmarks/Program.cs | 4 + Tests/MQTTnet.Benchmarks/TcpPipesBenchmark.cs | 8 +- .../MQTTnet.TestApp.AspNetCore2.csproj | 1 + 15 files changed, 174 insertions(+), 31 deletions(-) create mode 100644 Frameworks/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs rename {Tests/MQTTnet.Benchmarks => Frameworks/MQTTnet.AspnetCore/Client}/Tcp/BufferExtensions.cs (93%) rename {Tests/MQTTnet.Benchmarks => Frameworks/MQTTnet.AspnetCore/Client}/Tcp/DuplexPipe.cs (96%) rename {Tests/MQTTnet.Benchmarks => Frameworks/MQTTnet.AspnetCore/Client}/Tcp/SocketAwaitable.cs (97%) rename {Tests/MQTTnet.Benchmarks => Frameworks/MQTTnet.AspnetCore/Client}/Tcp/SocketReceiver.cs (96%) rename {Tests/MQTTnet.Benchmarks => Frameworks/MQTTnet.AspnetCore/Client}/Tcp/SocketSender.cs (98%) rename {Tests/MQTTnet.Benchmarks => Frameworks/MQTTnet.AspnetCore/Client}/Tcp/TcpConnection.cs (85%) create mode 100644 Tests/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs diff --git a/Frameworks/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs b/Frameworks/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs new file mode 100644 index 00000000..e308ccd4 --- /dev/null +++ b/Frameworks/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs @@ -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(); + } + } + } + } +} diff --git a/Tests/MQTTnet.Benchmarks/Tcp/BufferExtensions.cs b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/BufferExtensions.cs similarity index 93% rename from Tests/MQTTnet.Benchmarks/Tcp/BufferExtensions.cs rename to Frameworks/MQTTnet.AspnetCore/Client/Tcp/BufferExtensions.cs index 879306c1..5911a3a1 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/BufferExtensions.cs +++ b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/BufferExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.InteropServices; -namespace MQTTnet.Benchmarks.Tcp +namespace MQTTnet.AspNetCore.Client.Tcp { public static class BufferExtensions { diff --git a/Tests/MQTTnet.Benchmarks/Tcp/DuplexPipe.cs b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/DuplexPipe.cs similarity index 96% rename from Tests/MQTTnet.Benchmarks/Tcp/DuplexPipe.cs rename to Frameworks/MQTTnet.AspnetCore/Client/Tcp/DuplexPipe.cs index f5f3316d..e234da55 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/DuplexPipe.cs +++ b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/DuplexPipe.cs @@ -1,6 +1,6 @@ using System.IO.Pipelines; -namespace MQTTnet.Benchmarks.Tcp +namespace MQTTnet.AspNetCore.Client.Tcp { public class DuplexPipe : IDuplexPipe { diff --git a/Tests/MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs similarity index 97% rename from Tests/MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs rename to Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs index 2271bd76..96160d13 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/SocketAwaitable.cs +++ b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs @@ -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 { diff --git a/Tests/MQTTnet.Benchmarks/Tcp/SocketReceiver.cs b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs similarity index 96% rename from Tests/MQTTnet.Benchmarks/Tcp/SocketReceiver.cs rename to Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs index bc8e5c03..219b7222 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/SocketReceiver.cs +++ b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs @@ -2,7 +2,7 @@ using System.IO.Pipelines; using System.Net.Sockets; -namespace MQTTnet.Benchmarks.Tcp +namespace MQTTnet.AspNetCore.Client.Tcp { public class SocketReceiver { diff --git a/Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs similarity index 98% rename from Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs rename to Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs index 7cb1bc1d..c8ba8326 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/SocketSender.cs +++ b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs @@ -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 { diff --git a/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs similarity index 85% rename from Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs rename to Frameworks/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs index 83cb98a8..40ba2959 100644 --- a/Tests/MQTTnet.Benchmarks/Tcp/TcpConnection.cs +++ b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs @@ -4,27 +4,35 @@ 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 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 +46,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 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() diff --git a/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs index 88bfcc91..f7acfa4d 100644 --- a/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Connections; using MQTTnet.Adapter; +using MQTTnet.AspNetCore.Client.Tcp; using MQTTnet.Packets; using MQTTnet.Serializer; using System; @@ -27,6 +28,10 @@ namespace MQTTnet.AspNetCore public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken) { + if (Connection is TcpConnection tcp && !tcp.IsConnected) + { + return tcp.StartAsync(); + } return Task.CompletedTask; } @@ -34,6 +39,7 @@ namespace MQTTnet.AspNetCore { Connection.Transport.Input.Complete(); Connection.Transport.Output.Complete(); + return Task.CompletedTask; } diff --git a/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs index 5b832539..04117103 100644 --- a/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs +++ b/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs @@ -76,13 +76,10 @@ namespace MQTTnet.AspNetCore } var bodySlice = copy.Slice(0, bodyLength); - using (var body = new MemoryStream(bodySlice.GetArray())) - { - packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, body)); - consumed = bodySlice.End; - observed = bodySlice.End; - return true; - } + packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, new MqttPacketBodyReader(bodySlice.GetArray()))); + consumed = bodySlice.End; + observed = bodySlice.End; + return true; } } } diff --git a/Source/MQTTnet/MqttFactory.cs b/Source/MQTTnet/MqttFactory.cs index 00438fa9..e830ad20 100644 --- a/Source/MQTTnet/MqttFactory.cs +++ b/Source/MQTTnet/MqttFactory.cs @@ -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(); diff --git a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index 5e612fb9..2a6b91a5 100644 --- a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -10,9 +10,11 @@ + + diff --git a/Tests/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs b/Tests/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs new file mode 100644 index 00000000..1e2fbb2b --- /dev/null +++ b/Tests/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs @@ -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(); + } + } + } +} diff --git a/Tests/MQTTnet.Benchmarks/Program.cs b/Tests/MQTTnet.Benchmarks/Program.cs index 53069163..9407ccfe 100644 --- a/Tests/MQTTnet.Benchmarks/Program.cs +++ b/Tests/MQTTnet.Benchmarks/Program.cs @@ -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(); break; + case '8': + BenchmarkRunner.Run(new AllowNonOptimized()); + break; } Console.ReadLine(); diff --git a/Tests/MQTTnet.Benchmarks/TcpPipesBenchmark.cs b/Tests/MQTTnet.Benchmarks/TcpPipesBenchmark.cs index cb3ffbaf..4252ab36 100644 --- a/Tests/MQTTnet.Benchmarks/TcpPipesBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/TcpPipesBenchmark.cs @@ -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; } diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj index f6ead870..a216d728 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj @@ -10,6 +10,7 @@ + From c32a606a7064aef784f9042604b0af16d5d3e8b1 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 2 Jun 2018 11:53:58 +0200 Subject: [PATCH 107/138] add memory diagnoser --- Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs b/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs index 53b71fd6..f6ff61a3 100644 --- a/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs @@ -9,6 +9,7 @@ namespace MQTTnet.Benchmarks { [ClrJob] [RPlotExporter, RankColumn] + [MemoryDiagnoser] public class MessageProcessingBenchmark { private IMqttServer _mqttServer; From cf947415e8c9b2e4bcd275642042ce030aaf5089 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 2 Jun 2018 11:54:22 +0200 Subject: [PATCH 108/138] enable abort exceptions --- .../MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Frameworks/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs index 40ba2959..4bf74fbb 100644 --- a/Frameworks/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs +++ b/Frameworks/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs @@ -131,14 +131,14 @@ namespace MQTTnet.AspNetCore.Client.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) @@ -153,7 +153,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp { if (_aborted) { - //error = error ?? new MqttCommunicationException(); + error = error ?? ConnectionAborted(); } _application.Output.Complete(error); @@ -193,6 +193,11 @@ namespace MQTTnet.AspNetCore.Client.Tcp } } + private Exception ConnectionAborted() + { + return new MqttCommunicationException("Connection Aborted"); + } + private async Task DoSend() { Exception error = null; From e65c3f2f2b84b47952f15824cfe961f897a21d43 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 16 Jun 2018 17:30:08 +0200 Subject: [PATCH 109/138] moved files into the correct place --- .../MqttConnectionContext.cs | 114 ----------------- .../MqttClientConnectionContextFactory.cs | 0 .../Client/Tcp/BufferExtensions.cs | 0 .../Client/Tcp/DuplexPipe.cs | 0 .../Client/Tcp/SocketAwaitable.cs | 0 .../Client/Tcp/SocketReceiver.cs | 0 .../Client/Tcp/SocketSender.cs | 0 .../Client/Tcp/TcpConnection.cs | 1 + .../ConnectionBuilderExtensions.cs | 0 .../MqttConnectionContext.cs | 120 ++++++++++++++++++ .../MqttConnectionHandler.cs | 0 .../MQTTnet.AspnetCore/ReaderExtensions.cs | 0 12 files changed, 121 insertions(+), 114 deletions(-) delete mode 100644 Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs rename {Frameworks => Source}/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs (100%) rename {Frameworks => Source}/MQTTnet.AspnetCore/Client/Tcp/BufferExtensions.cs (100%) rename {Frameworks => Source}/MQTTnet.AspnetCore/Client/Tcp/DuplexPipe.cs (100%) rename {Frameworks => Source}/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs (100%) rename {Frameworks => Source}/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs (100%) rename {Frameworks => Source}/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs (100%) rename {Frameworks => Source}/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs (99%) rename {Frameworks => Source}/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs (100%) create mode 100644 Source/MQTTnet.AspnetCore/MqttConnectionContext.cs rename {Frameworks => Source}/MQTTnet.AspnetCore/MqttConnectionHandler.cs (100%) rename {Frameworks => Source}/MQTTnet.AspnetCore/ReaderExtensions.cs (100%) diff --git a/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs deleted file mode 100644 index f7acfa4d..00000000 --- a/Frameworks/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Microsoft.AspNetCore.Connections; -using MQTTnet.Adapter; -using MQTTnet.AspNetCore.Client.Tcp; -using MQTTnet.Packets; -using MQTTnet.Serializer; -using System; -using System.Collections.Generic; -using System.IO.Pipelines; -using System.Threading; -using System.Threading.Tasks; - -namespace MQTTnet.AspNetCore -{ - public class MqttConnectionContext : IMqttChannelAdapter - { - public IMqttPacketSerializer PacketSerializer { get; } - public ConnectionContext Connection { get; } - - public string Endpoint => Connection.ConnectionId; - - public MqttConnectionContext( - IMqttPacketSerializer packetSerializer, - ConnectionContext connection) - { - PacketSerializer = packetSerializer; - Connection = connection; - } - - 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 void Dispose() - { - } - - public async Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) - { - var input = Connection.Transport.Input; - - while (!cancellationToken.IsCancellationRequested) - { - ReadResult readResult; - ReadingPacketStarted?.Invoke(this, EventArgs.Empty); - - 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 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); - ReadingPacketCompleted?.Invoke(this, EventArgs.Empty); - } - } - - cancellationToken.ThrowIfCancellationRequested(); - return null; - } - - public async Task SendPacketsAsync(TimeSpan timeout, IEnumerable packets, CancellationToken cancellationToken) - { - foreach (var packet in packets) - { - var buffer = PacketSerializer.Serialize(packet); - await Connection.Transport.Output.WriteAsync(buffer.AsMemory()); - } - } - - public event EventHandler ReadingPacketStarted; - public event EventHandler ReadingPacketCompleted; - } -} diff --git a/Frameworks/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs rename to Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs diff --git a/Frameworks/MQTTnet.AspnetCore/Client/Tcp/BufferExtensions.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/BufferExtensions.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/Client/Tcp/BufferExtensions.cs rename to Source/MQTTnet.AspnetCore/Client/Tcp/BufferExtensions.cs diff --git a/Frameworks/MQTTnet.AspnetCore/Client/Tcp/DuplexPipe.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/DuplexPipe.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/Client/Tcp/DuplexPipe.cs rename to Source/MQTTnet.AspnetCore/Client/Tcp/DuplexPipe.cs diff --git a/Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs rename to Source/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs diff --git a/Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs rename to Source/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs diff --git a/Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs rename to Source/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs diff --git a/Frameworks/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs similarity index 99% rename from Frameworks/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs rename to Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs index 4bf74fbb..7417e744 100644 --- a/Frameworks/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs +++ b/Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.IO.Pipelines; using System.Net; diff --git a/Frameworks/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs b/Source/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs rename to Source/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs new file mode 100644 index 00000000..3d42937f --- /dev/null +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -0,0 +1,120 @@ +using Microsoft.AspNetCore.Connections; +using MQTTnet.Adapter; +using MQTTnet.AspNetCore.Client.Tcp; +using MQTTnet.Packets; +using MQTTnet.Serializer; +using System; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; + +namespace MQTTnet.AspNetCore +{ + public class MqttConnectionContext : IMqttChannelAdapter + { + public IMqttPacketSerializer PacketSerializer { get; } + public ConnectionContext Connection { get; } + + public string Endpoint => Connection.ConnectionId; + + public MqttConnectionContext( + IMqttPacketSerializer packetSerializer, + ConnectionContext connection) + { + PacketSerializer = packetSerializer; + Connection = connection; + } + + 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 void Dispose() + { + } + + public async Task 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 event EventHandler ReadingPacketStarted; + public event EventHandler ReadingPacketCompleted; + } +} diff --git a/Frameworks/MQTTnet.AspnetCore/MqttConnectionHandler.cs b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/MqttConnectionHandler.cs rename to Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs diff --git a/Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs similarity index 100% rename from Frameworks/MQTTnet.AspnetCore/ReaderExtensions.cs rename to Source/MQTTnet.AspnetCore/ReaderExtensions.cs From 410bc211a546f7d2e424b45270c0e13c2825ff22 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 16 Jun 2018 17:39:04 +0200 Subject: [PATCH 110/138] fixed packet reader --- Source/MQTTnet/Serializer/MqttPacketReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MQTTnet/Serializer/MqttPacketReader.cs b/Source/MQTTnet/Serializer/MqttPacketReader.cs index d50fae5d..79329561 100644 --- a/Source/MQTTnet/Serializer/MqttPacketReader.cs +++ b/Source/MQTTnet/Serializer/MqttPacketReader.cs @@ -19,7 +19,7 @@ namespace MQTTnet.Serializer 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) From d573423372fa57ba2830c73ea866efdaad7d10fc Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 16 Jun 2018 17:39:37 +0200 Subject: [PATCH 111/138] downgrade dependencies to 2.0 --- Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj | 4 ++-- Tests/MQTTnet.TestApp.AspNetCore2/Program.cs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj index 9754f40c..8db250bf 100644 --- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj +++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs index a7164117..5248ce61 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs @@ -13,7 +13,10 @@ namespace MQTTnet.TestApp.AspNetCore2 private static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) - .UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt())) + .UseKestrel(o => { + o.ListenAnyIP(1883, l => l.UseMqtt()); + o.ListenAnyIP(5000); // default http pipeline + }) .UseStartup() .Build(); } From beb54acc2fd4f0f7148830040e49bede55e6371c Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 17 Jun 2018 13:19:43 +0200 Subject: [PATCH 112/138] Refactor serializer benchmark to allow fair comparisons. --- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 2 +- .../Serializer/MqttPacketBodyReader.cs | 3 ++- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 24 +++++-------------- .../MqttPacketSerializerTests.cs | 2 +- 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 7db9a2a7..b21f4b30 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -192,7 +192,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 { diff --git a/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs b/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs index 04c0a222..d751c688 100644 --- a/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs +++ b/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs @@ -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; diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index 06e7b397..5696c6a0 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -46,29 +46,17 @@ namespace MQTTnet.Benchmarks { for (var i = 0; i < 10000; i++) { - using (var headerStream = new MemoryStream(Join(_serializedPacket))) + using (var stream = new MemoryStream()) { - var channel = new TestMqttChannel(headerStream); + stream.Write(_serializedPacket.Array, _serializedPacket.Offset, _serializedPacket.Count); + stream.Position = 0; + + var channel = new TestMqttChannel(stream); 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()))); - } + _serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(_serializedPacket.Array, _serializedPacket.Count - header.RemainingLength))); } } } - - private static byte[] Join(params ArraySegment[] chunks) - { - var buffer = new MemoryStream(); - foreach (var chunk in chunks) - { - buffer.Write(chunk.Array, chunk.Offset, chunk.Count); - } - - return buffer.ToArray(); - } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 40aa9535..0a0fa2b0 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -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))); From 56e9e15ac006876cac3ff4d97607a0f4e0349b92 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 17 Jun 2018 13:47:58 +0200 Subject: [PATCH 113/138] Refactor Serializer memory usage. --- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 2 + .../Serializer/IMqttPacketSerializer.cs | 2 + .../Serializer/MqttPacketSerializer.cs | 73 ++++++++++++------- Source/MQTTnet/Serializer/MqttPacketWriter.cs | 21 ++++++ .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 55 +++++++++++--- 5 files changed, 117 insertions(+), 36 deletions(-) diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index b21f4b30..cfbf743d 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -95,6 +95,8 @@ namespace MQTTnet.Adapter var packetData = PacketSerializer.Serialize(packet); await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false); + + PacketSerializer.FreeBuffer(); } catch (Exception exception) { diff --git a/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs b/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs index a81071bd..e3066f9f 100644 --- a/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs +++ b/Source/MQTTnet/Serializer/IMqttPacketSerializer.cs @@ -11,5 +11,7 @@ namespace MQTTnet.Serializer ArraySegment Serialize(MqttBasePacket mqttPacket); MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket); + + void FreeBuffer(); } } \ No newline at end of file diff --git a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs b/Source/MQTTnet/Serializer/MqttPacketSerializer.cs index 811acf70..aafaa90f 100644 --- a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttPacketSerializer.cs @@ -11,15 +11,19 @@ namespace MQTTnet.Serializer { private const int FixedHeaderSize = 1; + [ThreadStatic] + private static MqttPacketWriter _packetWriter; + public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; public ArraySegment Serialize(MqttBasePacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); - var packetWriter = new MqttPacketWriter(); + var packetWriter = InitializePacketWriter(); // Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes) + packetWriter.Reset(); packetWriter.Seek(5); var fixedHeader = SerializePacket(packet, packetWriter); @@ -39,28 +43,6 @@ namespace MQTTnet.Serializer return new ArraySegment(buffer, headerOffset, packetWriter.Length - headerOffset); } - 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."); - } - } - public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) { if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket)); @@ -92,6 +74,43 @@ namespace MQTTnet.Serializer } } + public void FreeBuffer() + { + InitializePacketWriter().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 MqttPacketWriter InitializePacketWriter() + { + if (_packetWriter == null) + { + _packetWriter = new MqttPacketWriter(); + } + + return _packetWriter; + } + private static MqttBasePacket DeserializeUnsubAck(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -310,7 +329,7 @@ namespace MQTTnet.Serializer var packet = new MqttConnAckPacket(); var acknowledgeFlags = body.ReadByte(); - + if (ProtocolVersion == MqttProtocolVersion.V311) { packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0; @@ -372,7 +391,7 @@ namespace MQTTnet.Serializer connectFlags |= 0x20; } } - + if (packet.Password != null && packet.Username == null) { 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]."); @@ -387,7 +406,7 @@ namespace MQTTnet.Serializer { connectFlags |= 0x80; } - + packetWriter.Write(connectFlags); packetWriter.Write(packet.KeepAlivePeriod); packetWriter.WriteWithLengthPrefix(packet.ClientId); @@ -424,7 +443,7 @@ namespace MQTTnet.Serializer { connectAcknowledgeFlags |= 0x1; } - + packetWriter.Write(connectAcknowledgeFlags); } else diff --git a/Source/MQTTnet/Serializer/MqttPacketWriter.cs b/Source/MQTTnet/Serializer/MqttPacketWriter.cs index c0c49fcb..a2c6f5bf 100644 --- a/Source/MQTTnet/Serializer/MqttPacketWriter.cs +++ b/Source/MQTTnet/Serializer/MqttPacketWriter.cs @@ -93,6 +93,11 @@ namespace MQTTnet.Serializer IncreasePostition(count); } + public void Reset() + { + Length = 0; + } + public void Seek(int offset) { EnsureCapacity(offset); @@ -104,6 +109,22 @@ namespace MQTTnet.Serializer 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 < 4096) + { + return; + } + + Array.Resize(ref _buffer, 4096); + } + private void EnsureAdditionalCapacity(int additionalCapacity) { var freeSpace = _buffer.Length - _position; diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index 5696c6a0..2e9f22c3 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -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,6 +39,7 @@ namespace MQTTnet.Benchmarks for (var i = 0; i < 10000; i++) { _serializer.Serialize(_packet); + _serializer.FreeBuffer(); } } @@ -46,16 +48,51 @@ namespace MQTTnet.Benchmarks { for (var i = 0; i < 10000; i++) { - using (var stream = new MemoryStream()) - { - stream.Write(_serializedPacket.Array, _serializedPacket.Offset, _serializedPacket.Count); - stream.Position = 0; + var channel = new BenchmarkMqttChannel(_serializedPacket); - var channel = new TestMqttChannel(stream); + var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); + _serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(_serializedPacket.Array, _serializedPacket.Count - header.RemainingLength))); + } + } - var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); - _serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(_serializedPacket.Array, _serializedPacket.Count - header.RemainingLength))); - } + private class BenchmarkMqttChannel : IMqttChannel + { + private readonly ArraySegment _buffer; + private int _position; + + public BenchmarkMqttChannel(ArraySegment buffer) + { + _buffer = buffer; + _position = _buffer.Offset; + } + + public string Endpoint { get; } + + public Task ConnectAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task DisconnectAsync() + { + throw new NotImplementedException(); + } + + public Task 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() + { } } } From baac26c5f6f9033803af69d1b5daa72f4a076c7b Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 17 Jun 2018 14:36:32 +0200 Subject: [PATCH 114/138] Refactor package deserialization. --- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 4 --- Source/MQTTnet/Serializer/Extensions.cs | 5 ++- Source/MQTTnet/Serializer/MqttFixedHeader.cs | 4 +-- Source/MQTTnet/Serializer/MqttPacketReader.cs | 33 ++++++++++++++----- .../Serializer/MqttPacketSerializer.cs | 9 +++-- .../MessageProcessingBenchmark.cs | 1 + .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 16 +++++++-- 7 files changed, 50 insertions(+), 22 deletions(-) diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index cfbf743d..94e0f332 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -157,10 +157,6 @@ namespace MQTTnet.Adapter private async Task ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken) { var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, cancellationToken).ConfigureAwait(false); - if (fixedHeader == null) - { - return null; - } try { diff --git a/Source/MQTTnet/Serializer/Extensions.cs b/Source/MQTTnet/Serializer/Extensions.cs index 1a14de4f..00ad7cfb 100644 --- a/Source/MQTTnet/Serializer/Extensions.cs +++ b/Source/MQTTnet/Serializer/Extensions.cs @@ -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; } diff --git a/Source/MQTTnet/Serializer/MqttFixedHeader.cs b/Source/MQTTnet/Serializer/MqttFixedHeader.cs index d87f63d4..a8c2015b 100644 --- a/Source/MQTTnet/Serializer/MqttFixedHeader.cs +++ b/Source/MQTTnet/Serializer/MqttFixedHeader.cs @@ -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; } } } diff --git a/Source/MQTTnet/Serializer/MqttPacketReader.cs b/Source/MQTTnet/Serializer/MqttPacketReader.cs index d50fae5d..5bbed528 100644 --- a/Source/MQTTnet/Serializer/MqttPacketReader.cs +++ b/Source/MQTTnet/Serializer/MqttPacketReader.cs @@ -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,29 @@ namespace MQTTnet.Serializer { public static class MqttPacketReader { + [ThreadStatic] + private static byte[] _fixedHeaderBuffer; + + [ThreadStatic] + private static byte[] _singleByteBuffer; + public static async Task 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. // 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 = new byte[2]; + 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; + throw new TaskCanceledException(); } ExceptionHelper.ThrowGracefulSocketClose(); @@ -56,7 +63,7 @@ namespace MQTTnet.Serializer // 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 = ReadByteAsync(channel, cancellationToken).GetAwaiter().GetResult(); + encodedByte = ReadByte(channel, cancellationToken); value += (byte)(encodedByte & 127) * multiplier; if (multiplier > 128 * 128 * 128) @@ -70,10 +77,10 @@ namespace MQTTnet.Serializer return value; } - private static async Task 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) { ExceptionHelper.ThrowGracefulSocketClose(); @@ -81,5 +88,15 @@ namespace MQTTnet.Serializer return buffer[0]; } + + private static byte[] InitializeFixedHeaderBuffer() + { + return _fixedHeaderBuffer ?? (_fixedHeaderBuffer = new byte[2]); + } + + private static byte[] InitializeSingleByteBuffer() + { + return _singleByteBuffer ?? (_singleByteBuffer = new byte[1]); + } } } diff --git a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs b/Source/MQTTnet/Serializer/MqttPacketSerializer.cs index aafaa90f..159ba895 100644 --- a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttPacketSerializer.cs @@ -199,19 +199,18 @@ namespace MQTTnet.Serializer private static MqttBasePacket DeserializePublish(ReceivedMqttPacket receivedMqttPacket) { - var body = receivedMqttPacket.Body; - ThrowIfBodyIsEmpty(body); + ThrowIfBodyIsEmpty(receivedMqttPacket.Body); 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 @@ -219,7 +218,7 @@ namespace MQTTnet.Serializer PacketIdentifier = packetIdentifier, Retain = retain, Topic = topic, - Payload = body.ReadRemainingData().ToArray(), + Payload = receivedMqttPacket.Body.ReadRemainingData().ToArray(), QualityOfServiceLevel = qualityOfServiceLevel, Dup = dup }; diff --git a/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs b/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs index 53b71fd6..f6ff61a3 100644 --- a/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs @@ -9,6 +9,7 @@ namespace MQTTnet.Benchmarks { [ClrJob] [RPlotExporter, RankColumn] + [MemoryDiagnoser] public class MessageProcessingBenchmark { private IMqttServer _mqttServer; diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index 2e9f22c3..28de60ff 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -46,12 +46,19 @@ namespace MQTTnet.Benchmarks [Benchmark] public void Deserialize_10000_Messages() { + var channel = new BenchmarkMqttChannel(_serializedPacket); + for (var i = 0; i < 10000; i++) { - var channel = new BenchmarkMqttChannel(_serializedPacket); + channel.Reset(); var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); - _serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(_serializedPacket.Array, _serializedPacket.Count - header.RemainingLength))); + + var receivedPacket = new ReceivedMqttPacket( + header.Flags, + new MqttPacketBodyReader(_serializedPacket.Array, _serializedPacket.Count - header.RemainingLength)); + + _serializer.Deserialize(receivedPacket); } } @@ -68,6 +75,11 @@ namespace MQTTnet.Benchmarks public string Endpoint { get; } + public void Reset() + { + _position = _buffer.Offset; + } + public Task ConnectAsync(CancellationToken cancellationToken) { throw new NotImplementedException(); From 66048931a2bae650582e638b010cee0104a797f8 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 17 Jun 2018 17:19:08 +0200 Subject: [PATCH 115/138] Refactor ASP net Core integration and fix breaking change. --- .../Client/Tcp/SocketAwaitable.cs | 3 +- .../Client/Tcp/SocketReceiver.cs | 1 - .../Client/Tcp/SocketSender.cs | 1 - .../Client/Tcp/TcpConnection.cs | 6 -- .../MqttConnectionContext.cs | 25 +++---- Source/MQTTnet.AspnetCore/MqttHostedServer.cs | 3 +- Source/MQTTnet.AspnetCore/ReaderExtensions.cs | 75 +++++++++---------- 7 files changed, 51 insertions(+), 63 deletions(-) diff --git a/Source/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs index 96160d13..dbc26120 100644 --- a/Source/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs +++ b/Source/MQTTnet.AspnetCore/Client/Tcp/SocketAwaitable.cs @@ -23,9 +23,10 @@ namespace MQTTnet.AspNetCore.Client.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)); diff --git a/Source/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs index 219b7222..7d11fa2b 100644 --- a/Source/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs +++ b/Source/MQTTnet.AspnetCore/Client/Tcp/SocketReceiver.cs @@ -24,7 +24,6 @@ namespace MQTTnet.AspNetCore.Client.Tcp _eventArgs.SetBuffer(buffer); #else var segment = buffer.GetArray(); - _eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); #endif if (!_socket.ReceiveAsync(_eventArgs)) diff --git a/Source/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs index c8ba8326..55192d6c 100644 --- a/Source/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs +++ b/Source/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs @@ -61,7 +61,6 @@ namespace MQTTnet.AspNetCore.Client.Tcp _eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory)); #else var segment = memory.GetArray(); - _eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); #endif if (!_socket.SendAsync(_eventArgs)) diff --git a/Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs index 7417e744..37913fe4 100644 --- a/Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs +++ b/Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs @@ -21,13 +21,9 @@ namespace MQTTnet.AspNetCore.Client.Tcp private Socket _socket; private IDuplexPipe _application; - public bool IsConnected { get; private set; } - public override string ConnectionId { get; set; } - public override IFeatureCollection Features { get; } - public override IDictionary Items { get; set; } public override IDuplexPipe Transport { get; set; } @@ -209,11 +205,9 @@ namespace MQTTnet.AspNetCore.Client.Tcp } catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted) { - error = null; } catch (ObjectDisposedException) { - error = null; } catch (IOException ex) { diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs index 3d42937f..8a8469de 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -4,7 +4,6 @@ using MQTTnet.AspNetCore.Client.Tcp; using MQTTnet.Packets; using MQTTnet.Serializer; using System; -using System.Collections.Generic; using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; @@ -13,11 +12,6 @@ namespace MQTTnet.AspNetCore { public class MqttConnectionContext : IMqttChannelAdapter { - public IMqttPacketSerializer PacketSerializer { get; } - public ConnectionContext Connection { get; } - - public string Endpoint => Connection.ConnectionId; - public MqttConnectionContext( IMqttPacketSerializer packetSerializer, ConnectionContext connection) @@ -26,6 +20,12 @@ namespace MQTTnet.AspNetCore 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) @@ -43,10 +43,6 @@ namespace MQTTnet.AspNetCore return Task.CompletedTask; } - public void Dispose() - { - } - public async Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) { var input = Connection.Transport.Input; @@ -106,15 +102,16 @@ namespace MQTTnet.AspNetCore 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 event EventHandler ReadingPacketStarted; - public event EventHandler ReadingPacketCompleted; + + public void Dispose() + { + } } } diff --git a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs index 708d3832..4b6b436b 100644 --- a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs +++ b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs @@ -13,7 +13,8 @@ namespace MQTTnet.AspNetCore { private readonly IMqttServerOptions _options; - public MqttHostedServer(IMqttServerOptions options, IEnumerable adapters, IMqttNetLogger logger) : base(adapters, logger.CreateChildLogger(nameof(MqttHostedServer))) + public MqttHostedServer(IMqttServerOptions options, IEnumerable adapters, IMqttNetLogger logger) + : base(adapters, logger.CreateChildLogger(nameof(MqttHostedServer))) { _options = options ?? throw new ArgumentNullException(nameof(options)); } diff --git a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs index 04117103..2b7d8a67 100644 --- a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Buffers; -using System.IO; using MQTTnet.Adapter; using MQTTnet.Exceptions; using MQTTnet.Packets; @@ -8,8 +7,43 @@ using MQTTnet.Serializer; namespace MQTTnet.AspNetCore { - public static class ReaderExtensions + public static class ReaderExtensions { + public static bool TryDeserialize(this IMqttPacketSerializer serializer, in ReadOnlySequence 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 input) + { + if (input.IsSingleSegment) + { + return input.First.Span.ToArray(); + } + + // Should be rare + return input.ToArray(); + } + private static bool TryReadBodyLength(ref ReadOnlySequence 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. @@ -44,42 +78,5 @@ namespace MQTTnet.AspNetCore result = value; return true; } - - - - public static byte[] GetArray(this in ReadOnlySequence input) - { - if (input.IsSingleSegment) - { - return input.First.Span.ToArray(); - } - - // Should be rare - return input.ToArray(); - } - - public static bool TryDeserialize(this IMqttPacketSerializer serializer, in ReadOnlySequence 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()))); - consumed = bodySlice.End; - observed = bodySlice.End; - return true; - } } } From bc20850fba2fbf13e9d60d748c2dd30ad2278c17 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 19 Jun 2018 20:52:31 +0200 Subject: [PATCH 116/138] Refactor serializer locking and thread instances. --- Build/MQTTnet.AspNetCore.nuspec | 6 +-- Build/MQTTnet.Extensions.ManagedClient.nuspec | 19 +-------- Build/MQTTnet.Extensions.Rpc.nuspec | 19 +-------- Build/MQTTnet.nuspec | 2 +- Build/build.ps1 | 8 ++-- Build/upload.ps1 | 9 ++++ MQTTnet.sln | 2 + .../ManagedMqttClient.cs | 41 ++++++++----------- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 7 ++++ .../MQTTnet/Diagnostics/MqttNetChildLogger.cs | 2 +- .../Implementations/MqttTcpChannel.Uwp.cs | 22 ++++++++-- Source/MQTTnet/MQTTnet.csproj | 5 ++- .../Serializer/MqttPacketSerializer.cs | 35 +++++----------- Source/MQTTnet/Serializer/MqttPacketWriter.cs | 6 ++- .../Server/MqttClientSessionsManager.cs | 2 +- .../MainPage.xaml | 31 +++++++++----- .../MainPage.xaml.cs | 20 ++++++++- 17 files changed, 123 insertions(+), 113 deletions(-) create mode 100644 Build/upload.ps1 diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec index 9f595626..669f205c 100644 --- a/Build/MQTTnet.AspNetCore.nuspec +++ b/Build/MQTTnet.AspNetCore.nuspec @@ -13,11 +13,9 @@ * Updated to MQTTnet 2.8.0. Copyright Christian Kratky 2016-2018 - 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 + 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 - - - + diff --git a/Build/MQTTnet.Extensions.ManagedClient.nuspec b/Build/MQTTnet.Extensions.ManagedClient.nuspec index 714ed9e1..6d7f7f69 100644 --- a/Build/MQTTnet.Extensions.ManagedClient.nuspec +++ b/Build/MQTTnet.Extensions.ManagedClient.nuspec @@ -15,24 +15,7 @@ Copyright Christian Kratky 2016-2018 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 - - - - - - - - - - - - - - - - - - + diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec index 16a51c2f..d7eddb00 100644 --- a/Build/MQTTnet.Extensions.Rpc.nuspec +++ b/Build/MQTTnet.Extensions.Rpc.nuspec @@ -15,24 +15,7 @@ Copyright Christian Kratky 2016-2018 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 - - - - - - - - - - - - - - - - - - + diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 5d584377..556957d9 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -48,7 +48,7 @@ - + diff --git a/Build/build.ps1 b/Build/build.ps1 index 59f3112c..0b6f074f 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -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 } \ No newline at end of file diff --git a/Build/upload.ps1 b/Build/upload.ps1 new file mode 100644 index 00000000..a7cb1728 --- /dev/null +++ b/Build/upload.ps1 @@ -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 +} \ No newline at end of file diff --git a/MQTTnet.sln b/MQTTnet.sln index 0e0e60c3..a46c295b 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -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 diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 9bd14b21..687b0cdc 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -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 _messageQueue = new BlockingCollection(); - private readonly Dictionary _subscriptions = new Dictionary(); - private readonly AsyncLock _subscriptionsLock = new AsyncLock(); + private readonly ConcurrentDictionary _subscriptions = new ConcurrentDictionary(); private readonly List _unsubscriptions = new List(); private readonly IMqttClient _mqttClient; @@ -118,39 +116,36 @@ namespace MQTTnet.Extensions.ManagedClient _messageQueue.Add(applicationMessage); } - public async Task SubscribeAsync(IEnumerable topicFilters) + public Task SubscribeAsync(IEnumerable topicFilters) { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - using (await _subscriptionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + foreach (var topicFilter in topicFilters) { - foreach (var topicFilter in topicFilters) + _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; + _subscriptionsNotPushed = true; + } + + return Task.FromResult(0); + } + + public Task UnsubscribeAsync(IEnumerable topics) + { + foreach (var topic in topics) + { + if (_subscriptions.TryRemove(topic, out _)) { - _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; + _unsubscriptions.Add(topic); _subscriptionsNotPushed = true; } } - } - public async Task UnsubscribeAsync(IEnumerable topics) - { - using (await _subscriptionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) - { - foreach (var topic in topics) - { - if (_subscriptions.Remove(topic)) - { - _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 subscriptions; List unsubscriptions; - using (await _subscriptionsLock.LockAsync(CancellationToken.None).ConfigureAwait(false)) + lock (_subscriptions) { subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList(); diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 94e0f332..61912ef0 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -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; @@ -88,6 +90,7 @@ namespace MQTTnet.Adapter public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken) { + await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { _logger.Verbose("TX >>> {0}", packet); @@ -107,6 +110,10 @@ namespace MQTTnet.Adapter WrapException(exception); } + finally + { + _writerSemaphore.Release(); + } } public async Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) diff --git a/Source/MQTTnet/Diagnostics/MqttNetChildLogger.cs b/Source/MQTTnet/Diagnostics/MqttNetChildLogger.cs index 1ae5a9c3..37334543 100644 --- a/Source/MQTTnet/Diagnostics/MqttNetChildLogger.cs +++ b/Source/MQTTnet/Diagnostics/MqttNetChildLogger.cs @@ -9,7 +9,7 @@ namespace MQTTnet.Diagnostics public MqttNetChildLogger(IMqttNetLogger logger, string source) { - _logger = logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _source = source; } diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs index d7a030ab..2ccc7d97 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs @@ -40,7 +40,18 @@ namespace MQTTnet.Implementations public static Func> 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() diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 4afd09d0..83643782 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -1,7 +1,7 @@  - netstandard1.3;netstandard2.0;net452;uap10.0 + netstandard1.3;netstandard2.0;net452;net461;uap10.0 netstandard1.3;netstandard2.0 MQTTnet MQTTnet @@ -62,4 +62,7 @@ + + + \ No newline at end of file diff --git a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs b/Source/MQTTnet/Serializer/MqttPacketSerializer.cs index 159ba895..537513db 100644 --- a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttPacketSerializer.cs @@ -11,8 +11,7 @@ namespace MQTTnet.Serializer { private const int FixedHeaderSize = 1; - [ThreadStatic] - private static MqttPacketWriter _packetWriter; + private readonly MqttPacketWriter _packetWriter = new MqttPacketWriter(); public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; @@ -20,14 +19,12 @@ namespace MQTTnet.Serializer { if (packet == null) throw new ArgumentNullException(nameof(packet)); - var packetWriter = InitializePacketWriter(); - // Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes) - packetWriter.Reset(); - packetWriter.Seek(5); + _packetWriter.Reset(); + _packetWriter.Seek(5); - var fixedHeader = SerializePacket(packet, packetWriter); - var remainingLength = packetWriter.Length - 5; + var fixedHeader = SerializePacket(packet, _packetWriter); + var remainingLength = _packetWriter.Length - 5; var remainingLengthBuffer = MqttPacketWriter.EncodeRemainingLength(remainingLength); @@ -35,12 +32,12 @@ namespace MQTTnet.Serializer var headerOffset = 5 - headerSize; // 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); + _packetWriter.Seek(headerOffset); + _packetWriter.Write(fixedHeader); + _packetWriter.Write(remainingLengthBuffer.Array, remainingLengthBuffer.Offset, remainingLengthBuffer.Count); - var buffer = packetWriter.GetBuffer(); - return new ArraySegment(buffer, headerOffset, packetWriter.Length - headerOffset); + var buffer = _packetWriter.GetBuffer(); + return new ArraySegment(buffer, headerOffset, _packetWriter.Length - headerOffset); } public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) @@ -76,7 +73,7 @@ namespace MQTTnet.Serializer public void FreeBuffer() { - InitializePacketWriter().FreeBuffer(); + _packetWriter.FreeBuffer(); } private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter packetWriter) @@ -101,16 +98,6 @@ namespace MQTTnet.Serializer } } - private static MqttPacketWriter InitializePacketWriter() - { - if (_packetWriter == null) - { - _packetWriter = new MqttPacketWriter(); - } - - return _packetWriter; - } - private static MqttBasePacket DeserializeUnsubAck(MqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); diff --git a/Source/MQTTnet/Serializer/MqttPacketWriter.cs b/Source/MQTTnet/Serializer/MqttPacketWriter.cs index a2c6f5bf..ba535bfb 100644 --- a/Source/MQTTnet/Serializer/MqttPacketWriter.cs +++ b/Source/MQTTnet/Serializer/MqttPacketWriter.cs @@ -12,6 +12,8 @@ namespace MQTTnet.Serializer /// public class MqttPacketWriter { + public static int MaxBufferSize = 4096; + private byte[] _buffer = new byte[128]; private int _position; @@ -117,12 +119,12 @@ namespace MQTTnet.Serializer // 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 < 4096) + if (_buffer.Length < MaxBufferSize) { return; } - Array.Resize(ref _buffer, 4096); + Array.Resize(ref _buffer, MaxBufferSize); } private void EnsureAdditionalCapacity(int additionalCapacity) diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 1cb301ab..5937265e 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -148,7 +148,7 @@ namespace MQTTnet.Server clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage); } } - catch (TaskCanceledException) + catch (OperationCanceledException) { } catch (Exception exception) diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml index 906d9b8a..41617df8 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml @@ -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 @@ Keep alive interval: - + TCP WS @@ -142,6 +144,7 @@ Persist retained messages in JSON format Clear previously retained messages on startup + Allow persistent sessions @@ -149,11 +152,6 @@ - - - - - @@ -162,12 +160,23 @@ - + - - - - + + + + + + + + + + + + + diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 76f947d4..387364a5 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -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 _traceMessages = new ConcurrentQueue(); + private readonly ObservableCollection _sessions = new ObservableCollection(); 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() From 64b38487bc09a6a6f566e30df20b60bc39f1009f Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Wed, 20 Jun 2018 08:20:54 +0200 Subject: [PATCH 117/138] improve session manager --- .../Server/MqttClientSessionsManager.cs | 65 +++++++++++++------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 1cb301ab..1b507d3e 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -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,7 +15,11 @@ namespace MQTTnet.Server public class MqttClientSessionsManager { private readonly BlockingCollection _messageQueue = new BlockingCollection(); - private readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); + + /// + /// manual locking dictionaries is faster than using concurrent dictionary + /// + private readonly Dictionary _sessions = new Dictionary(); private readonly CancellationToken _cancellationToken; @@ -43,12 +48,16 @@ namespace MQTTnet.Server public Task StopAsync() { - foreach (var session in _sessions) + lock (_sessions) { - session.Value.Stop(MqttClientDisconnectType.NotClean); - } + foreach (var session in _sessions) + { + session.Value.Stop(MqttClientDisconnectType.NotClean); + } - _sessions.Clear(); + _sessions.Clear(); + } + _messageQueue.Dispose(); return Task.FromResult(0); } @@ -60,10 +69,11 @@ namespace MQTTnet.Server public Task> GetClientStatusAsync() { var result = new List(); - foreach (var session in _sessions) + + foreach (var session in GetSessions()) { - var status = new MqttClientSessionStatus(this, session.Value); - session.Value.FillStatus(status); + var status = new MqttClientSessionStatus(this, session); + session.FillStatus(status); result.Add(status); } @@ -83,12 +93,15 @@ namespace MQTTnet.Server if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - if (!_sessions.TryGetValue(clientId, out var session)) + lock (_sessions) { - throw new InvalidOperationException($"Client session '{clientId}' is unknown."); - } + if (!_sessions.TryGetValue(clientId, out var session)) + { + throw new InvalidOperationException($"Client session '{clientId}' is unknown."); + } - return session.SubscribeAsync(topicFilters); + return session.SubscribeAsync(topicFilters); + } } public Task UnsubscribeAsync(string clientId, IList topicFilters) @@ -96,17 +109,23 @@ namespace MQTTnet.Server if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - if (!_sessions.TryGetValue(clientId, out var session)) + lock (_sessions) { - throw new InvalidOperationException($"Client session '{clientId}' is unknown."); - } + if (!_sessions.TryGetValue(clientId, out var session)) + { + throw new InvalidOperationException($"Client session '{clientId}' is unknown."); + } - return session.UnsubscribeAsync(topicFilters); + return session.UnsubscribeAsync(topicFilters); + } } public void DeleteSession(string clientId) { - _sessions.TryRemove(clientId, out _); + lock (_sessions) + { + _sessions.Remove(clientId); + } _logger.Verbose("Session for client '{0}' deleted.", clientId); } @@ -143,7 +162,7 @@ namespace MQTTnet.Server _retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).GetAwaiter().GetResult(); } - foreach (var clientSession in _sessions.Values) + foreach (var clientSession in GetSessions()) { clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage); } @@ -158,6 +177,14 @@ namespace MQTTnet.Server } } + private List GetSessions() + { + lock (_sessions) + { + return _sessions.Values.ToList(); + } + } + private async Task RunSession(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) { var clientId = string.Empty; @@ -263,7 +290,7 @@ namespace MQTTnet.Server { if (connectPacket.CleanSession) { - _sessions.TryRemove(connectPacket.ClientId, out _); + _sessions.Remove(connectPacket.ClientId); clientSession.Stop(MqttClientDisconnectType.Clean); clientSession.Dispose(); From 0322660561ed93060385d83d9fa1f2d7e533aeb6 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 20 Jun 2018 20:07:25 +0200 Subject: [PATCH 118/138] Refactor async/await and ConcurrentDictionary usage. --- README.md | 8 +- .../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 4 +- Source/MQTTnet/Client/MqttClient.cs | 13 +- .../MQTTnet/Internal/AsyncAutoResetEvent.cs | 33 ++--- Source/MQTTnet/Internal/AsyncLock.cs | 2 +- Source/MQTTnet/Serializer/MqttPacketReader.cs | 9 +- Source/MQTTnet/Server/IMqttClientSession.cs | 23 +++ .../Server/MqttClientKeepAliveMonitor.cs | 26 ++-- .../Server/MqttClientPendingPacketsQueue.cs | 58 +++++--- Source/MQTTnet/Server/MqttClientSession.cs | 138 +++++++++++------- .../Server/MqttClientSessionsManager.cs | 14 +- .../Server/MqttClientSubscriptionsManager.cs | 49 ++++--- .../Server/MqttEnqueuedApplicationMessage.cs | 10 +- .../Server/MqttRetainedMessagesManager.cs | 65 +++++---- Source/MQTTnet/Server/MqttServer.cs | 10 +- .../MqttKeepAliveMonitorTests.cs | 82 ++++++++--- .../MqttSubscriptionsManagerTests.cs | 42 +----- .../PerformanceTest.cs | 43 +++++- Tests/MQTTnet.TestApp.NetCore/Program.cs | 14 +- Tests/MQTTnet.TestApp.NetCore/ServerTest.cs | 11 +- 20 files changed, 409 insertions(+), 245 deletions(-) create mode 100644 Source/MQTTnet/Server/IMqttClientSession.cs diff --git a/README.md b/README.md index 52735cae..d50f51c2 100644 --- a/README.md +++ b/README.md @@ -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, ) * 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, ) - +* HA4IoT (Open Source Home Automation system for .NET, ) If you use this library and want to see your project here please let me know. diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index 717f74ea..ebf66ec1 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -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 { diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index e9362f6d..ccedb5ae 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -271,21 +271,16 @@ namespace MQTTnet.Client private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested) - { - throw new TaskCanceledException(); - } + cancellationToken.ThrowIfCancellationRequested(); _sendTracker.Restart(); + return _adapter.SendPacketAsync(packet, cancellationToken); } private async Task SendAndReceiveAsync(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket { - if (cancellationToken.IsCancellationRequested) - { - throw new TaskCanceledException(); - } + cancellationToken.ThrowIfCancellationRequested(); _sendTracker.Restart(); @@ -524,7 +519,7 @@ namespace MQTTnet.Client { await task.ConfigureAwait(false); } - catch (TaskCanceledException) + catch (OperationCanceledException) { } } diff --git a/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs b/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs index 132e7e05..a90b98e4 100644 --- a/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs +++ b/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs @@ -11,8 +11,10 @@ namespace MQTTnet.Internal private readonly LinkedList> _waiters = new LinkedList>(); 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. + lock (_waiters) { - // We timed-out; remove our reference to the task. - // This is an O(n) operation since waiters is a LinkedList. - 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(); } } diff --git a/Source/MQTTnet/Internal/AsyncLock.cs b/Source/MQTTnet/Internal/AsyncLock.cs index 87878fa9..87571c2a 100644 --- a/Source/MQTTnet/Internal/AsyncLock.cs +++ b/Source/MQTTnet/Internal/AsyncLock.cs @@ -17,7 +17,7 @@ namespace MQTTnet.Internal public Task LockAsync(CancellationToken cancellationToken) { - Task wait = _semaphore.WaitAsync(cancellationToken); + var wait = _semaphore.WaitAsync(cancellationToken); return wait.IsCompleted ? _releaser : wait.ContinueWith((_, state) => (IDisposable)state, diff --git a/Source/MQTTnet/Serializer/MqttPacketReader.cs b/Source/MQTTnet/Serializer/MqttPacketReader.cs index 5bbed528..826747cf 100644 --- a/Source/MQTTnet/Serializer/MqttPacketReader.cs +++ b/Source/MQTTnet/Serializer/MqttPacketReader.cs @@ -29,11 +29,7 @@ namespace MQTTnet.Serializer var bytesRead = await channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false); if (bytesRead <= 0) { - if (cancellationToken.IsCancellationRequested) - { - throw new TaskCanceledException(); - } - + cancellationToken.ThrowIfCancellationRequested(); ExceptionHelper.ThrowGracefulSocketClose(); } @@ -59,6 +55,8 @@ namespace MQTTnet.Serializer while ((encodedByte & 128) != 0) { + 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 @@ -83,6 +81,7 @@ namespace MQTTnet.Serializer var readCount = channel.ReadAsync(buffer, 0, 1, cancellationToken).GetAwaiter().GetResult(); if (readCount <= 0) { + cancellationToken.ThrowIfCancellationRequested(); ExceptionHelper.ThrowGracefulSocketClose(); } diff --git a/Source/MQTTnet/Server/IMqttClientSession.cs b/Source/MQTTnet/Server/IMqttClientSession.cs new file mode 100644 index 00000000..a94ad185 --- /dev/null +++ b/Source/MQTTnet/Server/IMqttClientSession.cs @@ -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 RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter); + void Stop(MqttClientDisconnectType disconnectType); + + Task SubscribeAsync(IList topicFilters); + Task UnsubscribeAsync(IList topicFilters); + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs b/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs index a4130889..b3628612 100644 --- a/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs +++ b/Source/MQTTnet/Server/MqttClientKeepAliveMonitor.cs @@ -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); } } } diff --git a/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs b/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs index 7a55c0b2..503d9926 100644 --- a/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs +++ b/Source/MQTTnet/Server/MqttClientPendingPacketsQueue.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Concurrent; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; @@ -13,13 +13,13 @@ namespace MQTTnet.Server { public class MqttClientPendingPacketsQueue : IDisposable { + private readonly Queue _queue = new Queue(); private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent(); + private readonly IMqttServerOptions _options; private readonly MqttClientSession _clientSession; private readonly IMqttNetChildLogger _logger; - private ConcurrentQueue _queue = new ConcurrentQueue(); - public MqttClientPendingPacketsQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); @@ -29,7 +29,16 @@ namespace MQTTnet.Server _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(); - 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(packet, cancellationToken).ConfigureAwait(false); + adapter.SendPacketAsync(packet, cancellationToken).GetAwaiter().GetResult(); _logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); } diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index 460bea49..429fdb45 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -12,7 +12,7 @@ using MQTTnet.Protocol; namespace MQTTnet.Server { - public class MqttClientSession : IDisposable + public class MqttClientSession : IMqttClientSession { private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); @@ -47,7 +47,7 @@ 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); _pendingPacketsQueue = new MqttClientPendingPacketsQueue(_options, this, _logger); } @@ -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,7 +149,7 @@ namespace MQTTnet.Server if (_willMessage != null && !_wasCleanDisconnect) { - _sessionsManager.EnqueueApplicationMessage(this, _willMessage); + _sessionsManager.EnqueueApplicationMessage(this, _willMessage.ToPublishPacket()); } _willMessage = null; @@ -160,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) { @@ -184,15 +190,19 @@ 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; } - + _pendingPacketsQueue.Enqueue(publishPacket); } @@ -233,21 +243,29 @@ namespace MQTTnet.Server _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(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) @@ -257,40 +275,41 @@ namespace MQTTnet.Server PacketIdentifier = pubRecPacket.PacketIdentifier }; - return adapter.SendPacketAsync(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 topicFilters) @@ -298,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(subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false); + adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).GetAwaiter().GetResult(); if (subscribeResult.CloseConnection) { @@ -316,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(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.EnqueueApplicationMessage(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: { @@ -348,27 +367,40 @@ namespace MQTTnet.Server } } - private Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) + private void HandleIncomingPublishPacketWithQoS0(MqttPublishPacket publishPacket) { - _sessionsManager.EnqueueApplicationMessage(this, applicationMessage); - - var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }; - return adapter.SendPacketAsync(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) + { + _sessionsManager.EnqueueApplicationMessage(this, publishPacket); + + var response = new MqttPubAckPacket + { + PacketIdentifier = publishPacket.PacketIdentifier + }; + + adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult(); + } + + private void HandleIncomingPublishPacketWithQoS2( + IMqttChannelAdapter adapter, + MqttPublishPacket publishPacket, + CancellationToken cancellationToken) { // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) - _sessionsManager.EnqueueApplicationMessage(this, applicationMessage); + _sessionsManager.EnqueueApplicationMessage(this, publishPacket); - var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; - return adapter.SendPacketAsync(response, cancellationToken); - } + var response = new MqttPubRecPacket + { + PacketIdentifier = publishPacket.PacketIdentifier + }; - private static Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) - { - var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }; - return adapter.SendPacketAsync(response, cancellationToken); + adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult(); } private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 5937265e..8188da82 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; +using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -41,7 +42,7 @@ namespace MQTTnet.Server Task.Factory.StartNew(() => ProcessQueuedApplicationMessages(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); } - public Task StopAsync() + public void Stop() { foreach (var session in _sessions) { @@ -49,7 +50,6 @@ namespace MQTTnet.Server } _sessions.Clear(); - return Task.FromResult(0); } public Task StartSession(IMqttChannelAdapter clientAdapter) @@ -71,11 +71,11 @@ namespace MQTTnet.Server return Task.FromResult((IList)result); } - 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)); - _messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, applicationMessage), _cancellationToken); + _messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, publishPacket), _cancellationToken); } public Task SubscribeAsync(string clientId, IList topicFilters) @@ -118,7 +118,7 @@ namespace MQTTnet.Server { var enqueuedApplicationMessage = _messageQueue.Take(cancellationToken); var sender = enqueuedApplicationMessage.Sender; - var applicationMessage = enqueuedApplicationMessage.ApplicationMessage; + var applicationMessage = enqueuedApplicationMessage.PublishPacket.ToApplicationMessage(); var interceptorContext = InterceptApplicationMessage(sender, applicationMessage); if (interceptorContext != null) @@ -145,7 +145,7 @@ namespace MQTTnet.Server foreach (var clientSession in _sessions.Values) { - clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage); + clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage.ToPublishPacket()); } } catch (OperationCanceledException) diff --git a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs index 5f067ff6..83ac0339 100644 --- a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs @@ -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 _subscriptions = new ConcurrentDictionary(); + private readonly Dictionary _subscriptions = new Dictionary(); 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(); - foreach (var subscription in _subscriptions) - { - if (!MqttTopicFilterComparer.IsMatch(applicationMessage.Topic, subscription.Key)) - { - continue; - } - qosLevels.Add(subscription.Value); + lock (_subscriptions) + { + foreach (var subscription in _subscriptions) + { + if (!MqttTopicFilterComparer.IsMatch(topic, subscription.Key)) + { + continue; + } + + qosLevels.Add(subscription.Value); + } } if (qosLevels.Count == 0) @@ -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 subscribedQoSLevels) + private static CheckSubscriptionsResult CreateSubscriptionResult(MqttQualityOfServiceLevel qosLevel, HashSet subscribedQoSLevels) { MqttQualityOfServiceLevel effectiveQoS; - if (subscribedQoSLevels.Contains(applicationMessage.QualityOfServiceLevel)) + if (subscribedQoSLevels.Contains(qosLevel)) { - effectiveQoS = applicationMessage.QualityOfServiceLevel; + effectiveQoS = qosLevel; } else if (subscribedQoSLevels.Count == 1) { diff --git a/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs b/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs index 20ff2feb..37591d89 100644 --- a/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs +++ b/Source/MQTTnet/Server/MqttEnqueuedApplicationMessage.cs @@ -1,15 +1,17 @@ -namespace MQTTnet.Server +using MQTTnet.Packets; + +namespace MQTTnet.Server { public class MqttEnqueuedApplicationMessage { - public MqttEnqueuedApplicationMessage(MqttClientSession sender, MqttApplicationMessage applicationMessage) + public MqttEnqueuedApplicationMessage(MqttClientSession sender, MqttPublishPacket publishPacket) { Sender = sender; - ApplicationMessage = applicationMessage; + PublishPacket = publishPacket; } public MqttClientSession Sender { get; } - public MqttApplicationMessage ApplicationMessage { get; } + public MqttPublishPacket PublishPacket { get; } } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs index 9fe454cf..86c321fc 100644 --- a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs @@ -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 _messages = new ConcurrentDictionary(); + private readonly Dictionary _messages = new Dictionary(); + 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(); - 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) + lock (_messages) { - saveIsRequired = _messages.TryRemove(applicationMessage.Topic, out _); - _logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); - } - else - { - 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 messages; + lock (_messages) + { + messages = _messages.Values.ToList(); + } + + await _options.Storage.SaveRetainedMessagesAsync(messages).ConfigureAwait(false); } } } diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index 38e631e9..69850d59 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -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.EnqueueApplicationMessage(null, applicationMessage); + _clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage.ToPublishPacket()); return Task.FromResult(0); } @@ -104,22 +105,23 @@ 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 { + _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; + _retainedMessagesManager = null; _clientSessionsManager = null; } diff --git a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs index 9563a41f..267b7bca 100644 --- a/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttKeepAliveMonitorTests.cs @@ -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 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); // 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 RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) + { + throw new NotSupportedException(); + } + + public void Stop(MqttClientDisconnectType disconnectType) + { + StopCalledCount++; + } + + public Task SubscribeAsync(IList topicFilters) + { + throw new NotSupportedException(); + } + + public Task UnsubscribeAsync(IList topicFilters) + { + throw new NotSupportedException(); + } + + public void Dispose() + { + } } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs index aa84dd3e..268e5fee 100644 --- a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs @@ -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); } } } diff --git a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index 752feb58..3f4a1d54 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -12,7 +12,48 @@ namespace MQTTnet.TestApp.NetCore { public static class PerformanceTest { - public static void Run() + public static void RunClientOnly() + { + 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 { diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 25302c7b..908844fa 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -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,7 @@ namespace MQTTnet.TestApp.NetCore } else if (pressedKey.KeyChar == '3') { - PerformanceTest.Run(); + PerformanceTest.RunClientAndServer(); return; } else if (pressedKey.KeyChar == '4') @@ -53,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); } diff --git a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs index 80a8e5a4..12fa1aa7 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs @@ -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 => From dddc1ec112713b22196ac99981a4a1f5b8a9fdf6 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 21 Jun 2018 08:12:55 -0400 Subject: [PATCH 119/138] Fix a bug where an incoming SSL client connection was being treated as a plain-text connection. --- Source/MQTTnet/Implementations/MqttTcpServerListener.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs index fccd77f3..f8a90b5d 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs @@ -70,15 +70,17 @@ namespace MQTTnet.Implementations #endif clientSocket.NoDelay = true; + SslStream sslStream = null; + if (_tlsCertificate != null) { - var sslStream = new SslStream(new NetworkStream(clientSocket), false); + sslStream = new SslStream(new NetworkStream(clientSocket), false); await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); } _logger.Verbose($"Client '{clientSocket.RemoteEndPoint}' accepted by TCP listener '{_socket.LocalEndPoint}, {_addressFamily}'."); - var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger); + var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (ObjectDisposedException) From e8d5bd7bfb06348f35a12a6bb9a568cf30101f25 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 21 Jun 2018 20:42:44 +0200 Subject: [PATCH 120/138] Add new properties and events for the managed client. --- Build/MQTTnet.AspNetCore.nuspec | 2 +- Build/MQTTnet.Extensions.ManagedClient.nuspec | 2 +- Build/MQTTnet.Extensions.Rpc.nuspec | 2 +- Build/MQTTnet.nuspec | 3 + .../IManagedMqttClient.cs | 4 ++ .../ManagedMqttClient.cs | 64 ++++++++++--------- .../MqttManagedProcessFailedEventArgs.cs | 14 ++++ 7 files changed, 58 insertions(+), 33 deletions(-) create mode 100644 Source/MQTTnet.Extensions.ManagedClient/MqttManagedProcessFailedEventArgs.cs diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec index 669f205c..6054370b 100644 --- a/Build/MQTTnet.AspNetCore.nuspec +++ b/Build/MQTTnet.AspNetCore.nuspec @@ -15,7 +15,7 @@ Copyright Christian Kratky 2016-2018 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 - + diff --git a/Build/MQTTnet.Extensions.ManagedClient.nuspec b/Build/MQTTnet.Extensions.ManagedClient.nuspec index 6d7f7f69..ba5333f2 100644 --- a/Build/MQTTnet.Extensions.ManagedClient.nuspec +++ b/Build/MQTTnet.Extensions.ManagedClient.nuspec @@ -15,7 +15,7 @@ Copyright Christian Kratky 2016-2018 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 - + diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec index d7eddb00..0df8e7e6 100644 --- a/Build/MQTTnet.Extensions.Rpc.nuspec +++ b/Build/MQTTnet.Extensions.Rpc.nuspec @@ -15,7 +15,7 @@ Copyright Christian Kratky 2016-2018 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 - + diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 556957d9..22cd3ae1 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -21,6 +21,9 @@ * [ManagedClient] The managed client is moved to a separate nuget package. * [ManagedClient] Added an own message format with extended properties like ID (BREAKING CHANGE). * [ManagedClient] Fixed a loading issue of stored application messages (thanks to @JTrotta). +* [ManagedClient] Added a new event which is fired when a synchronization of the subscriptions has failed. +* [ManagedClient] Added a new event which is fired when a connection attempt has failed. +* [ManagedClient] Exposed a new property which provides the count of not published messages (pending messages count). * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot). * [Server] The takeover of an existing client sessions is now treated as a _clean_ disconnect of the previous client. * [Server] The pending messages queue per client is now limited to 250 messages. Overflow strategy and count can be changed via options (thanks to @VladimirAkopyan) diff --git a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs index e4eaa687..b5a6c50b 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs @@ -9,12 +9,16 @@ namespace MQTTnet.Extensions.ManagedClient { bool IsStarted { get; } bool IsConnected { get; } + int PendingApplicationMessagesCount { get; } event EventHandler Connected; event EventHandler Disconnected; event EventHandler ApplicationMessageProcessed; + event EventHandler ConnectingFailed; + event EventHandler SynchronizingSubscriptionsFailed; + Task StartAsync(IManagedMqttClientOptions options); Task StopAsync(); diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 687b0cdc..bcd98ae7 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -14,8 +14,8 @@ namespace MQTTnet.Extensions.ManagedClient public class ManagedMqttClient : IManagedMqttClient { private readonly BlockingCollection _messageQueue = new BlockingCollection(); - private readonly ConcurrentDictionary _subscriptions = new ConcurrentDictionary(); - private readonly List _unsubscriptions = new List(); + private readonly Dictionary _subscriptions = new Dictionary(); + private readonly HashSet _unsubscriptions = new HashSet(); private readonly IMqttClient _mqttClient; private readonly IMqttNetChildLogger _logger; @@ -43,12 +43,16 @@ namespace MQTTnet.Extensions.ManagedClient public bool IsConnected => _mqttClient.IsConnected; public bool IsStarted => _connectionCancellationToken != null; + public int PendingApplicationMessagesCount => _messageQueue.Count; public event EventHandler Connected; public event EventHandler Disconnected; + public event EventHandler ApplicationMessageReceived; public event EventHandler ApplicationMessageProcessed; - public event EventHandler SynchronizingSubscriptionsFailed; + + public event EventHandler ConnectingFailed; + public event EventHandler SynchronizingSubscriptionsFailed; public async Task StartAsync(IManagedMqttClientOptions options) { @@ -120,10 +124,13 @@ namespace MQTTnet.Extensions.ManagedClient { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - foreach (var topicFilter in topicFilters) + lock (_subscriptions) { - _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; - _subscriptionsNotPushed = true; + foreach (var topicFilter in topicFilters) + { + _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; + _subscriptionsNotPushed = true; + } } return Task.FromResult(0); @@ -131,12 +138,17 @@ namespace MQTTnet.Extensions.ManagedClient public Task UnsubscribeAsync(IEnumerable topics) { - foreach (var topic in topics) + if (topics == null) throw new ArgumentNullException(nameof(topics)); + + lock (_subscriptions) { - if (_subscriptions.TryRemove(topic, out _)) + foreach (var topic in topics) { - _unsubscriptions.Add(topic); - _subscriptionsNotPushed = true; + if (_subscriptions.Remove(topic)) + { + _unsubscriptions.Add(topic); + _subscriptionsNotPushed = true; + } } } @@ -210,7 +222,7 @@ namespace MQTTnet.Extensions.ManagedClient } } - private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken) + private void PublishQueuedMessages(CancellationToken cancellationToken) { try { @@ -222,12 +234,9 @@ namespace MQTTnet.Extensions.ManagedClient continue; } - if (cancellationToken.IsCancellationRequested) - { - continue; - } + cancellationToken.ThrowIfCancellationRequested(); - await TryPublishQueuedMessageAsync(message).ConfigureAwait(false); + TryPublishQueuedMessage(message); } } catch (OperationCanceledException) @@ -243,17 +252,13 @@ namespace MQTTnet.Extensions.ManagedClient } } - private async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message) + private void TryPublishQueuedMessage(ManagedMqttApplicationMessage message) { Exception transmitException = null; try { - await _mqttClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false); - - if (_storageManager != null) - { - await _storageManager.RemoveAsync(message).ConfigureAwait(false); - } + _mqttClient.PublishAsync(message.ApplicationMessage).GetAwaiter().GetResult(); + _storageManager?.RemoveAsync(message).GetAwaiter().GetResult(); } catch (MqttCommunicationException exception) { @@ -282,13 +287,13 @@ namespace MQTTnet.Extensions.ManagedClient _logger.Info(nameof(ManagedMqttClient), "Synchronizing subscriptions"); List subscriptions; - List unsubscriptions; + HashSet unsubscriptions; lock (_subscriptions) { subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList(); - unsubscriptions = new List(_unsubscriptions); + unsubscriptions = new HashSet(_unsubscriptions); _unsubscriptions.Clear(); _subscriptionsNotPushed = false; @@ -316,7 +321,7 @@ namespace MQTTnet.Extensions.ManagedClient _logger.Warning(exception, "Synchronizing subscriptions failed."); _subscriptionsNotPushed = true; - SynchronizingSubscriptionsFailed?.Invoke(this, EventArgs.Empty); + SynchronizingSubscriptionsFailed?.Invoke(this, new MqttManagedProcessFailedEventArgs(exception)); } } @@ -332,8 +337,9 @@ namespace MQTTnet.Extensions.ManagedClient await _mqttClient.ConnectAsync(_options.ClientOptions).ConfigureAwait(false); return ReconnectionResult.Reconnected; } - catch (Exception) + catch (Exception exception) { + ConnectingFailed?.Invoke(this, new MqttManagedProcessFailedEventArgs(exception)); return ReconnectionResult.NotConnected; } } @@ -364,9 +370,7 @@ namespace MQTTnet.Extensions.ManagedClient _publishingCancellationToken = cts; -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(() => PublishQueuedMessagesAsync(cts.Token), cts.Token); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Task.Factory.StartNew(() => PublishQueuedMessages(cts.Token), cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); } private void StopPublishing() diff --git a/Source/MQTTnet.Extensions.ManagedClient/MqttManagedProcessFailedEventArgs.cs b/Source/MQTTnet.Extensions.ManagedClient/MqttManagedProcessFailedEventArgs.cs new file mode 100644 index 00000000..68dc1608 --- /dev/null +++ b/Source/MQTTnet.Extensions.ManagedClient/MqttManagedProcessFailedEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace MQTTnet.Extensions.ManagedClient +{ + public class MqttManagedProcessFailedEventArgs : EventArgs + { + public MqttManagedProcessFailedEventArgs(Exception exception) + { + Exception = exception; + } + + public Exception Exception { get; } + } +} From 6fa6abe6f08bcbe3952c71d368b7a82d5f9f5ec7 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 21 Jun 2018 20:42:49 +0200 Subject: [PATCH 121/138] Update readme. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index d50f51c2..c8cd14f3 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ [![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet) [![Build status](https://ci.appveyor.com/api/projects/status/ycit86voxfevm2aa/branch/master?svg=true)](https://ci.appveyor.com/project/chkr1011/mqttnet/branch/develop) [![BCH compliance](https://bettercodehub.com/edge/badge/chkr1011/MQTTnet?branch=master)](https://bettercodehub.com/) +[![OpenCollective](https://opencollective.com/mqttnet/backers/badge.svg)](#backers) +[![OpenCollective](https://opencollective.com/mqttnet/sponsors/badge.svg)](#sponsors) # MQTTnet @@ -73,6 +75,9 @@ Please find examples and the documentation at the Wiki of this repository ( Date: Thu, 21 Jun 2018 23:00:52 +0200 Subject: [PATCH 122/138] Update nuspec --- Build/MQTTnet.AspNetCore.nuspec | 2 +- Build/MQTTnet.Extensions.ManagedClient.nuspec | 2 +- Build/MQTTnet.Extensions.Rpc.nuspec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec index 6054370b..0ea2fc49 100644 --- a/Build/MQTTnet.AspNetCore.nuspec +++ b/Build/MQTTnet.AspNetCore.nuspec @@ -15,7 +15,7 @@ Copyright Christian Kratky 2016-2018 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 - + diff --git a/Build/MQTTnet.Extensions.ManagedClient.nuspec b/Build/MQTTnet.Extensions.ManagedClient.nuspec index ba5333f2..74d41d77 100644 --- a/Build/MQTTnet.Extensions.ManagedClient.nuspec +++ b/Build/MQTTnet.Extensions.ManagedClient.nuspec @@ -15,7 +15,7 @@ Copyright Christian Kratky 2016-2018 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 - + diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec index 0df8e7e6..840dbdb5 100644 --- a/Build/MQTTnet.Extensions.Rpc.nuspec +++ b/Build/MQTTnet.Extensions.Rpc.nuspec @@ -15,7 +15,7 @@ Copyright Christian Kratky 2016-2018 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 - + From ccea8e6eb58d3766df5aea84dedc715575aeeda3 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 21 Jun 2018 23:03:33 +0200 Subject: [PATCH 123/138] Update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c8cd14f3..7181d724 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ [![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet) [![Build status](https://ci.appveyor.com/api/projects/status/ycit86voxfevm2aa/branch/master?svg=true)](https://ci.appveyor.com/project/chkr1011/mqttnet/branch/develop) [![BCH compliance](https://bettercodehub.com/edge/badge/chkr1011/MQTTnet?branch=master)](https://bettercodehub.com/) -[![OpenCollective](https://opencollective.com/mqttnet/backers/badge.svg)](#backers) -[![OpenCollective](https://opencollective.com/mqttnet/sponsors/badge.svg)](#sponsors) +[![OpenCollective](https://opencollective.com/mqttnet/backers/badge.svg)](https://opencollective.com/mqttnet) +[![OpenCollective](https://opencollective.com/mqttnet/sponsors/badge.svg)](https://opencollective.com/mqttnet) # MQTTnet From 4fc1912ee851a3bd2ec5dc0cbc4d60827e9ae1ca Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Fri, 22 Jun 2018 09:21:33 +0200 Subject: [PATCH 124/138] fixed assembly versions --- Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj | 3 --- .../MQTTnet.Extensions.ManagedClient.csproj | 3 --- Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj | 3 --- Source/MQTTnet/MQTTnet.csproj | 3 --- 4 files changed, 12 deletions(-) diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj index 8db250bf..dd3a4d55 100644 --- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj +++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj @@ -2,9 +2,6 @@ netstandard2.0 - 0.0.0.0 - 0.0.0.0 - 0.0.0.0 diff --git a/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj b/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj index 3f050755..1a3d9cc9 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj +++ b/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj @@ -2,13 +2,10 @@ netstandard1.3;netstandard2.0;net452;net461;uap10.0 - 0.0.0.0 - 0.0.0.0 - 0.0.0.0 diff --git a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj index 3f050755..1a3d9cc9 100644 --- a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj +++ b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj @@ -2,13 +2,10 @@ netstandard1.3;netstandard2.0;net452;net461;uap10.0 - 0.0.0.0 - 0.0.0.0 - 0.0.0.0 diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 83643782..26bf0029 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -6,9 +6,6 @@ MQTTnet MQTTnet False - 0.0.0.0 - 0.0.0.0 - 0.0.0.0 From f39589a8c12a48f524220f134cda50b7aad6e5d7 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 22 Jun 2018 20:45:30 +0200 Subject: [PATCH 125/138] Add new publish overloads. --- Build/MQTTnet.nuspec | 1 + .../ApplicationMessagePublisherExtensions.cs | 23 +++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 22cd3ae1..a9000eb0 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -18,6 +18,7 @@ * [Client] Received messages are now processed completely in the worker thread without creating new Tasks. * [Client] Fixed wrong calculation for sending keep alive packets (thanks to @cstichlberger) * [Client] A clean disconnect (via DisconnectAsync) will no longer throw an exception. +* [Client] Added new overloads for quick message publishing. * [ManagedClient] The managed client is moved to a separate nuget package. * [ManagedClient] Added an own message format with extended properties like ID (BREAKING CHANGE). * [ManagedClient] Fixed a loading issue of stored application messages (thanks to @JTrotta). diff --git a/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs b/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs index dd166627..81dddbf1 100644 --- a/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs +++ b/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs @@ -14,7 +14,7 @@ namespace MQTTnet foreach (var applicationMessage in applicationMessages) { - await publisher.PublishAsync(applicationMessage); + await publisher.PublishAsync(applicationMessage).ConfigureAwait(false); } } @@ -25,7 +25,7 @@ namespace MQTTnet foreach (var applicationMessage in applicationMessages) { - await publisher.PublishAsync(applicationMessage); + await publisher.PublishAsync(applicationMessage).ConfigureAwait(false); } } @@ -35,8 +35,7 @@ namespace MQTTnet if (topic == null) throw new ArgumentNullException(nameof(topic)); return publisher.PublishAsync(builder => builder - .WithTopic(topic) - ); + .WithTopic(topic)); } public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload) @@ -46,8 +45,7 @@ namespace MQTTnet return publisher.PublishAsync(builder => builder .WithTopic(topic) - .WithPayload(payload) - ); + .WithPayload(payload)); } public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) @@ -55,11 +53,22 @@ namespace MQTTnet if (publisher == null) throw new ArgumentNullException(nameof(publisher)); if (topic == null) throw new ArgumentNullException(nameof(topic)); + return publisher.PublishAsync(builder => builder + .WithTopic(topic) + .WithPayload(payload) + .WithQualityOfServiceLevel(qualityOfServiceLevel)); + } + + public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain) + { + if (publisher == null) throw new ArgumentNullException(nameof(publisher)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + return publisher.PublishAsync(builder => builder .WithTopic(topic) .WithPayload(payload) .WithQualityOfServiceLevel(qualityOfServiceLevel) - ); + .WithRetainFlag(retain)); } public static Task PublishAsync(this IApplicationMessagePublisher publisher, Func builder) From 211853d51903490c1b0dea4cb539710d3fea9b04 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 24 Jun 2018 19:51:00 +0200 Subject: [PATCH 126/138] Fix wrong packet writer buffer length calculation. --- Source/MQTTnet/Serializer/MqttPacketWriter.cs | 12 ++++++--- .../MqttPacketWriterTests.cs | 25 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs diff --git a/Source/MQTTnet/Serializer/MqttPacketWriter.cs b/Source/MQTTnet/Serializer/MqttPacketWriter.cs index ba535bfb..f6784d4f 100644 --- a/Source/MQTTnet/Serializer/MqttPacketWriter.cs +++ b/Source/MQTTnet/Serializer/MqttPacketWriter.cs @@ -89,6 +89,8 @@ namespace MQTTnet.Serializer public void Write(byte[] array, int offset, int count) { + if (array == null) throw new ArgumentNullException(nameof(array)); + EnsureAdditionalCapacity(count); Array.Copy(array, offset, _buffer, _position, count); @@ -135,17 +137,18 @@ namespace MQTTnet.Serializer return; } - EnsureCapacity(additionalCapacity - freeSpace); + EnsureCapacity(_buffer.Length + additionalCapacity - freeSpace); } private void EnsureCapacity(int capacity) { - if (_buffer.Length >= capacity) + var newBufferLength = _buffer.Length; + + if (newBufferLength >= capacity) { return; } - - var newBufferLength = _buffer.Length; + while (newBufferLength < capacity) { newBufferLength *= 2; @@ -157,6 +160,7 @@ namespace MQTTnet.Serializer private void IncreasePostition(int length) { _position += length; + if (_position > Length) { Length = _position; diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs new file mode 100644 index 00000000..8b73b739 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/MqttPacketWriterTests.cs @@ -0,0 +1,25 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Serializer; + +namespace MQTTnet.Core.Tests +{ + [TestClass] + public class MqttPacketWriterTests + { + [TestMethod] + public void WritePacket() + { + var writer = new MqttPacketWriter(); + Assert.AreEqual(0, writer.Length); + + writer.WriteWithLengthPrefix("1234567890"); + Assert.AreEqual(10 + 2, writer.Length); + + writer.WriteWithLengthPrefix(new byte[300]); + Assert.AreEqual(300 + 2 + 12, writer.Length); + + writer.WriteWithLengthPrefix(new byte[5000]); + Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length); + } + } +} From 7240b86d984497fff09da3c6b8942cd2a668b54b Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Mon, 25 Jun 2018 09:25:29 +0200 Subject: [PATCH 127/138] fixed another build issue --- Build/build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/build.ps1 b/Build/build.ps1 index 0b6f074f..e57eb731 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -17,7 +17,7 @@ if ($path) { &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Build the ASP.NET Core 2.0 extension - &$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Build the RPC extension &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" From 4e9abd41c0680cef72b034d942f5f9f11e947382 Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Mon, 25 Jun 2018 12:31:19 +0200 Subject: [PATCH 128/138] improve error messages --- Source/MQTTnet/Serializer/MqttPacketBodyReader.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs b/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs index d751c688..2cf37dd4 100644 --- a/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs +++ b/Source/MQTTnet/Serializer/MqttPacketBodyReader.cs @@ -20,6 +20,7 @@ namespace MQTTnet.Serializer public byte ReadByte() { + ValidateReceiveBuffer(1); return _buffer[_offset++]; } @@ -30,6 +31,8 @@ namespace MQTTnet.Serializer public ushort ReadUInt16() { + ValidateReceiveBuffer(2); + var msb = _buffer[_offset++]; var lsb = _buffer[_offset++]; @@ -40,12 +43,22 @@ namespace MQTTnet.Serializer { var length = ReadUInt16(); + ValidateReceiveBuffer(length); + var result = new ArraySegment(_buffer, _offset, length); _offset += length; return result; } + private void ValidateReceiveBuffer(ushort length) + { + if (_buffer.Length < _offset + length) + { + throw new ArgumentOutOfRangeException(nameof(_buffer), $"expected at least {_offset + length} bytes but there are only {_buffer.Length} bytes"); + } + } + public string ReadStringWithLengthPrefix() { var buffer = ReadWithLengthPrefix(); From 4e40b2ddf31fd3bdd28a6e0ae3c039c5795dd993 Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Mon, 25 Jun 2018 12:31:47 +0200 Subject: [PATCH 129/138] added test to prove something is broken --- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 67 +++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index 7c4faf0b..ea258f56 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; +using MQTTnet.Implementations; namespace MQTTnet.Core.Tests { @@ -167,6 +168,72 @@ namespace MQTTnet.Core.Tests Assert.AreEqual(1, receivedMessagesCount); } + [TestMethod] + public async Task MqttServer_Publish_MultipleClients() + { + var serverAdapter = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger()); + var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); + var receivedMessagesCount = 0; + var locked = new object(); + + var clientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("localhost") + .Build(); + var clientOptions2 = new MqttClientOptionsBuilder() + .WithTcpServer("localhost") + .Build(); + + try + { + await s.StartAsync(new MqttServerOptions()); + + var c1 = new MqttFactory().CreateMqttClient(); + var c2 = new MqttFactory().CreateMqttClient(); + + await c1.ConnectAsync(clientOptions); + await c2.ConnectAsync(clientOptions2); + + c1.ApplicationMessageReceived += (_, __) => + { + lock (locked) + { + receivedMessagesCount++; + } + }; + + c2.ApplicationMessageReceived += (_, __) => + { + lock (locked) + { + receivedMessagesCount++; + } + }; + + var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); + await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce)); + await c2.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce)); + + //await Task.WhenAll(Publish(c1, message), Publish(c2, message)); + await Publish(c1, message); + + await Task.Delay(500); + } + finally + { + await s.StopAsync(); + } + + Assert.AreEqual(2000, receivedMessagesCount); + } + + private static async Task Publish(IMqttClient c1, MqttApplicationMessage message) + { + for (int i = 0; i < 1000; i++) + { + await c1.PublishAsync(message); + } + } + [TestMethod] public async Task MqttServer_RetainedMessagesFlow() { From 7dfb6fdc5242710e100c47f2f474752e1882018d Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Mon, 25 Jun 2018 14:51:46 +0200 Subject: [PATCH 130/138] fixed dup flag serialization and added roundtrip tests --- Source/MQTTnet/Packets/MqttPubAckPacket.cs | 2 +- .../Serializer/MqttPacketSerializer.cs | 2 +- .../MqttPacketSerializerTests.cs | 77 +++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/Source/MQTTnet/Packets/MqttPubAckPacket.cs b/Source/MQTTnet/Packets/MqttPubAckPacket.cs index 3015f278..5811849b 100644 --- a/Source/MQTTnet/Packets/MqttPubAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttPubAckPacket.cs @@ -4,7 +4,7 @@ { public override string ToString() { - return "PubAck"; + return $"PubAck [PacketIdentifier={PacketIdentifier}]"; } } } diff --git a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs b/Source/MQTTnet/Serializer/MqttPacketSerializer.cs index 537513db..f1eb790a 100644 --- a/Source/MQTTnet/Serializer/MqttPacketSerializer.cs +++ b/Source/MQTTnet/Serializer/MqttPacketSerializer.cs @@ -190,7 +190,7 @@ namespace MQTTnet.Serializer var retain = (receivedMqttPacket.FixedHeader & 0x1) > 0; var qualityOfServiceLevel = (MqttQualityOfServiceLevel)(receivedMqttPacket.FixedHeader >> 1 & 0x3); - var dup = (receivedMqttPacket.FixedHeader & 0x3) > 0; + var dup = (receivedMqttPacket.FixedHeader & 0x8) > 0; var topic = receivedMqttPacket.Body.ReadStringWithLengthPrefix(); diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 0a0fa2b0..61b69eb5 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -199,6 +199,64 @@ namespace MQTTnet.Core.Tests DeserializeAndCompare(p, "Ow4ABUEvQi9DAHtIRUxMTw=="); } + [TestMethod] + public void DeserializeV311_MqttPublishPacket_Qos1() + { + var p = new MqttPublishPacket + { + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, + }; + + var p2 = Roundtrip(p); + + Assert.AreEqual(p.QualityOfServiceLevel, p2.QualityOfServiceLevel); + Assert.AreEqual(p.Dup, p2.Dup); + } + + [TestMethod] + public void DeserializeV311_MqttPublishPacket_Qos2() + { + var p = new MqttPublishPacket + { + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, + PacketIdentifier = 1 + }; + + var p2 = Roundtrip(p); + + Assert.AreEqual(p.QualityOfServiceLevel, p2.QualityOfServiceLevel); + Assert.AreEqual(p.Dup, p2.Dup); + } + + [TestMethod] + public void DeserializeV311_MqttPublishPacket_Qos3() + { + var p = new MqttPublishPacket + { + QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce, + PacketIdentifier = 1 + }; + + var p2 = Roundtrip(p); + + Assert.AreEqual(p.QualityOfServiceLevel, p2.QualityOfServiceLevel); + Assert.AreEqual(p.Dup, p2.Dup); + } + + + [TestMethod] + public void DeserializeV311_MqttPublishPacket_DupFalse() + { + var p = new MqttPublishPacket + { + Dup = false, + }; + + var p2 = Roundtrip(p); + + Assert.AreEqual(p.Dup, p2.Dup); + } + [TestMethod] public void SerializeV311_MqttPubAckPacket() { @@ -430,6 +488,25 @@ namespace MQTTnet.Core.Tests } } + private static T Roundtrip(T packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) + where T : MqttBasePacket + { + var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion }; + + var buffer1 = serializer.Serialize(packet); + + using (var headerStream = new MemoryStream(Join(buffer1))) + { + var channel = new TestMqttChannel(headerStream); + var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); + + using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) + { + return (T)serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0))); + } + } + } + private static byte[] Join(params ArraySegment[] chunks) { var buffer = new MemoryStream(); From 5923c7c4085fed0755262fc3b85cb2755a554ae0 Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Mon, 25 Jun 2018 14:52:31 +0200 Subject: [PATCH 131/138] fixed negative length for empty packages --- Source/MQTTnet/Serializer/MqttPacketWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MQTTnet/Serializer/MqttPacketWriter.cs b/Source/MQTTnet/Serializer/MqttPacketWriter.cs index f6784d4f..c7f1bc2c 100644 --- a/Source/MQTTnet/Serializer/MqttPacketWriter.cs +++ b/Source/MQTTnet/Serializer/MqttPacketWriter.cs @@ -99,7 +99,7 @@ namespace MQTTnet.Serializer public void Reset() { - Length = 0; + Length = 5; } public void Seek(int offset) From b77798f8853e9fdf2f0c5e7591224a8100b9be22 Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Mon, 25 Jun 2018 15:31:49 +0200 Subject: [PATCH 132/138] dont use thread static buffers they are overlapping on server due to async io --- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 6 +++- Source/MQTTnet/Serializer/MqttPacketReader.cs | 35 +++++-------------- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 4 ++- .../MqttPacketReaderTests.cs | 4 ++- .../MqttPacketSerializerTests.cs | 9 +++-- 5 files changed, 27 insertions(+), 31 deletions(-) diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 61912ef0..0059f201 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -23,6 +23,10 @@ namespace MQTTnet.Adapter private readonly IMqttNetChildLogger _logger; private readonly IMqttChannel _channel; + private readonly byte[] _fixedHeaderBuffer = new byte[2]; + + private readonly byte[] _singleByteBuffer = new byte[1]; + private bool _isDisposed; public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetChildLogger logger) @@ -163,7 +167,7 @@ namespace MQTTnet.Adapter private async Task ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken) { - var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, cancellationToken).ConfigureAwait(false); + var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, _fixedHeaderBuffer, _singleByteBuffer, cancellationToken).ConfigureAwait(false); try { diff --git a/Source/MQTTnet/Serializer/MqttPacketReader.cs b/Source/MQTTnet/Serializer/MqttPacketReader.cs index 826747cf..32a92855 100644 --- a/Source/MQTTnet/Serializer/MqttPacketReader.cs +++ b/Source/MQTTnet/Serializer/MqttPacketReader.cs @@ -9,19 +9,13 @@ namespace MQTTnet.Serializer { public static class MqttPacketReader { - [ThreadStatic] - private static byte[] _fixedHeaderBuffer; - - [ThreadStatic] - private static byte[] _singleByteBuffer; - - public static async Task ReadFixedHeaderAsync(IMqttChannel channel, CancellationToken cancellationToken) + public static async Task ReadFixedHeaderAsync(IMqttChannel channel, byte[] fixedHeaderBuffer, byte[] singleByteBuffer, 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. // 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 buffer = fixedHeaderBuffer; var totalBytesRead = 0; while (totalBytesRead < buffer.Length) @@ -41,12 +35,12 @@ namespace MQTTnet.Serializer { return new MqttFixedHeader(buffer[0], 0); } - - var bodyLength = ReadBodyLength(channel, buffer[1], cancellationToken); + + var bodyLength = ReadBodyLength(channel, buffer[1], singleByteBuffer, cancellationToken); return new MqttFixedHeader(buffer[0], bodyLength); } - private static int ReadBodyLength(IMqttChannel channel, byte initialEncodedByte, CancellationToken cancellationToken) + private static int ReadBodyLength(IMqttChannel channel, byte initialEncodedByte, byte[] singleByteBuffer, 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; @@ -61,7 +55,7 @@ namespace MQTTnet.Serializer // 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); + encodedByte = ReadByte(channel, singleByteBuffer, cancellationToken); value += (byte)(encodedByte & 127) * multiplier; if (multiplier > 128 * 128 * 128) @@ -75,27 +69,16 @@ namespace MQTTnet.Serializer return value; } - private static byte ReadByte(IMqttChannel channel, CancellationToken cancellationToken) + private static byte ReadByte(IMqttChannel channel, byte[] singleByteBuffer, CancellationToken cancellationToken) { - var buffer = InitializeSingleByteBuffer(); - var readCount = channel.ReadAsync(buffer, 0, 1, cancellationToken).GetAwaiter().GetResult(); + var readCount = channel.ReadAsync(singleByteBuffer, 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]); + return singleByteBuffer[0]; } } } diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index 28de60ff..8beb7b49 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -47,12 +47,14 @@ namespace MQTTnet.Benchmarks public void Deserialize_10000_Messages() { var channel = new BenchmarkMqttChannel(_serializedPacket); + var fixedHeader = new byte[2]; + var singleByteBuffer = new byte[1]; for (var i = 0; i < 10000; i++) { channel.Reset(); - var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); + var header = MqttPacketReader.ReadFixedHeaderAsync(channel, fixedHeader, singleByteBuffer, CancellationToken.None).GetAwaiter().GetResult(); var receivedPacket = new ReceivedMqttPacket( header.Flags, diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs index f983622b..39aeeff2 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketReaderTests.cs @@ -14,7 +14,9 @@ namespace MQTTnet.Core.Tests [ExpectedException(typeof(MqttCommunicationClosedGracefullyException))] public void MqttPacketReader_EmptyStream() { - MqttPacketReader.ReadFixedHeaderAsync(new TestMqttChannel(new MemoryStream()), CancellationToken.None).GetAwaiter().GetResult(); + var fixedHeader = new byte[2]; + var singleByteBuffer = new byte[1]; + MqttPacketReader.ReadFixedHeaderAsync(new TestMqttChannel(new MemoryStream()), fixedHeader, singleByteBuffer, CancellationToken.None).GetAwaiter().GetResult(); } } } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index 61b69eb5..d8d23e29 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -476,7 +476,9 @@ namespace MQTTnet.Core.Tests using (var headerStream = new MemoryStream(Join(buffer1))) { var channel = new TestMqttChannel(headerStream); - var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); + var fixedHeader = new byte[2]; + var singleByteBuffer = new byte[1]; + var header = MqttPacketReader.ReadFixedHeaderAsync(channel, fixedHeader, singleByteBuffer, CancellationToken.None).GetAwaiter().GetResult(); using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) { @@ -498,7 +500,10 @@ namespace MQTTnet.Core.Tests using (var headerStream = new MemoryStream(Join(buffer1))) { var channel = new TestMqttChannel(headerStream); - var header = MqttPacketReader.ReadFixedHeaderAsync(channel, CancellationToken.None).GetAwaiter().GetResult(); + var fixedHeader = new byte[2]; + var singleByteBuffer = new byte[1]; + + var header = MqttPacketReader.ReadFixedHeaderAsync(channel, fixedHeader, singleByteBuffer, CancellationToken.None).GetAwaiter().GetResult(); using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) { From d5efb31e3aabfd11e116e32cf0490cd700f57ba4 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Mon, 25 Jun 2018 20:03:02 +0200 Subject: [PATCH 133/138] Fix build script. --- Build/build.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Build/build.ps1 b/Build/build.ps1 index 0b6f074f..63bae548 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -3,6 +3,10 @@ param([string]$assemblyVersion, [string]$nugetVersion) if ([string]::IsNullOrEmpty($assemblyVersion)) {$assemblyVersion = "0.0.1"} if ([string]::IsNullOrEmpty($nugetVersion)) {$nugetVersion = "0.0.1"} +Write-Host "Assembly version = $assemblyVersion" +Write-Host "Nuget version = $nugetVersion" +Write-Host + $vswhere = ${Env:\ProgramFiles(x86)} + '\Microsoft Visual Studio\Installer\vswhere' $path = &$vswhere -latest -products * -requires Microsoft.Component.MSBuild -property installationPath @@ -17,7 +21,7 @@ if ($path) { &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Build the ASP.NET Core 2.0 extension - &$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" + &$msbuild ..\Source\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Build the RPC extension &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" From b2bb5459478c9c0ca672f835f05bf6dd5231b68f Mon Sep 17 00:00:00 2001 From: JanEggers Date: Fri, 29 Jun 2018 13:06:46 +0200 Subject: [PATCH 134/138] Update MQTTnet.AspNetCore.nuspec added dependencies to nuspec --- Build/MQTTnet.AspNetCore.nuspec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec index 0ea2fc49..3e1e31bb 100644 --- a/Build/MQTTnet.AspNetCore.nuspec +++ b/Build/MQTTnet.AspNetCore.nuspec @@ -16,6 +16,9 @@ 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 + + + @@ -23,4 +26,4 @@ - \ No newline at end of file + From 8574bbb64fbb8efdbf4e91b4b13419d4f77a4bcb Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Fri, 29 Jun 2018 13:37:26 +0200 Subject: [PATCH 135/138] fixed parsing issue and added test --- MQTTnet.sln | 19 ++++++++ Source/MQTTnet.AspnetCore/ReaderExtensions.cs | 5 +++ .../MQTTnet.AspNetCore.Tests.csproj | 19 ++++++++ .../ReaderExtensionsTest.cs | 45 +++++++++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj create mode 100644 Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs diff --git a/MQTTnet.sln b/MQTTnet.sln index a46c295b..be6a0b6f 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Tests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedClient", "Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj", "{C400533A-8EBA-4F0B-BF4D-295C3708604B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", "Tests\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -208,6 +210,22 @@ Global {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|x64.Build.0 = Release|Any CPU {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|x86.ActiveCfg = Release|Any CPU {C400533A-8EBA-4F0B-BF4D-295C3708604B}.Release|x86.Build.0 = Release|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|ARM.ActiveCfg = Debug|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|ARM.Build.0 = Debug|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|x64.ActiveCfg = Debug|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|x64.Build.0 = Debug|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|x86.ActiveCfg = Debug|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Debug|x86.Build.0 = Debug|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|Any CPU.Build.0 = Release|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|ARM.ActiveCfg = Release|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|ARM.Build.0 = Release|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x64.ActiveCfg = Release|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x64.Build.0 = Release|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x86.ActiveCfg = Release|Any CPU + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -222,6 +240,7 @@ Global {C444E9C8-95FA-430E-9126-274129DE16CD} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587} {998D04DD-7CB0-45F5-A393-E2495C16399E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {C400533A-8EBA-4F0B-BF4D-295C3708604B} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587} + {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} diff --git a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs index 2b7d8a67..b893e43e 100644 --- a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs @@ -26,6 +26,11 @@ namespace MQTTnet.AspNetCore return false; } + if (copy.Length < bodyLength) + { + return false; + } + var bodySlice = copy.Slice(0, bodyLength); packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, new MqttPacketBodyReader(bodySlice.GetArray(), 0))); consumed = bodySlice.End; diff --git a/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj b/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj new file mode 100644 index 00000000..77dfff97 --- /dev/null +++ b/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + diff --git a/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs b/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs new file mode 100644 index 00000000..e6cdef23 --- /dev/null +++ b/Tests/MQTTnet.AspNetCore.Tests/ReaderExtensionsTest.cs @@ -0,0 +1,45 @@ +using System.Buffers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.AspNetCore; +using MQTTnet.Packets; +using MQTTnet.Serializer; + +namespace MQTTnet.AspNetCore.Tests +{ + [TestClass] + public class ReaderExtensionsTest + { + [TestMethod] + public void TestTryDeserialize() + { + var serializer = new MqttPacketSerializer(); + + var buffer = serializer.Serialize(new MqttPublishPacket() {Topic = "a", Payload = new byte[5]}); + + var sequence = new ReadOnlySequence(buffer.Array, buffer.Offset, buffer.Count); + + var part = sequence; + MqttBasePacket packet; + var consumed = part.Start; + var observed = part.Start; + var result = false; + + part = sequence.Slice(sequence.Start, 0); // empty message should fail + result = serializer.TryDeserialize(part, out packet, out consumed, out observed); + Assert.IsFalse(result); + + + part = sequence.Slice(sequence.Start, 1); // partial fixed header should fail + result = serializer.TryDeserialize(part, out packet, out consumed, out observed); + Assert.IsFalse(result); + + part = sequence.Slice(sequence.Start, 4); // partial body should fail + result = serializer.TryDeserialize(part, out packet, out consumed, out observed); + Assert.IsFalse(result); + + part = sequence; // complete msg should work + result = serializer.TryDeserialize(part, out packet, out consumed, out observed); + Assert.IsTrue(result); + } + } +} From c43acf7dacf4a5ddd133bb242cdb8f5d36cfd3a6 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Fri, 29 Jun 2018 20:05:48 +0200 Subject: [PATCH 136/138] more extensions for aspnetcore configuration --- .../ServiceCollectionExtensions.cs | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs index fa061eb7..8b65bbeb 100644 --- a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs @@ -13,26 +13,63 @@ namespace MQTTnet.AspNetCore public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, IMqttServerOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); + + services.AddSingleton(options); + services.AddHostedMqttServer(); + + return services; + } + + public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action configure) + { + var builder = new MqttServerOptionsBuilder(); + + configure(builder); + + services.AddSingleton(builder.Build()); + + services.AddHostedMqttServer(); + + return services; + } + + public static IServiceCollection AddHostedMqttServer(this IServiceCollection services) + where TOptions : class, IMqttServerOptions + { + services.AddSingleton(); + + services.AddHostedMqttServer(); + + return services; + } + + public static IServiceCollection AddHostedMqttServer(this IServiceCollection services) + { var logger = new MqttNetLogger(); var childLogger = logger.CreateChildLogger(); - services.AddSingleton(options); services.AddSingleton(logger); services.AddSingleton(childLogger); services.AddSingleton(); services.AddSingleton(s => s.GetService()); services.AddSingleton(s => s.GetService()); + + return services; + } + public static IServiceCollection AddMqttWebSocketServerAdapter(this IServiceCollection services) + { services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(s => s.GetService()); - if (options.DefaultEndpointOptions.IsEnabled) - { - services.AddSingleton(); - services.AddSingleton(s => s.GetService()); - } + return services; + } + + public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(s => s.GetService()); return services; } From 3d643aa4cb4684e89365e8581fa211b4c0710552 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 29 Jun 2018 23:54:34 +0200 Subject: [PATCH 137/138] Update versions. --- Build/MQTTnet.AspNetCore.nuspec | 2 +- Build/MQTTnet.Extensions.ManagedClient.nuspec | 2 +- Build/MQTTnet.Extensions.Rpc.nuspec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec index 3e1e31bb..c9936f35 100644 --- a/Build/MQTTnet.AspNetCore.nuspec +++ b/Build/MQTTnet.AspNetCore.nuspec @@ -15,7 +15,7 @@ Copyright Christian Kratky 2016-2018 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 - + diff --git a/Build/MQTTnet.Extensions.ManagedClient.nuspec b/Build/MQTTnet.Extensions.ManagedClient.nuspec index 74d41d77..ba5333f2 100644 --- a/Build/MQTTnet.Extensions.ManagedClient.nuspec +++ b/Build/MQTTnet.Extensions.ManagedClient.nuspec @@ -15,7 +15,7 @@ Copyright Christian Kratky 2016-2018 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 - + diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec index 840dbdb5..0df8e7e6 100644 --- a/Build/MQTTnet.Extensions.Rpc.nuspec +++ b/Build/MQTTnet.Extensions.Rpc.nuspec @@ -15,7 +15,7 @@ Copyright Christian Kratky 2016-2018 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 - + From 6ca6a52ae87975e39260a0794f9cd6e68fe59126 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 30 Jun 2018 00:29:04 +0200 Subject: [PATCH 138/138] Update docs. --- Build/MQTTnet.nuspec | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index a9000eb0..5cdf3d52 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -10,7 +10,8 @@ https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png false MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). - * [Core] Performance optimizations. + ** MQTTnet is now available at Open Collective for donations (https://opencollective.com/mqttnet). ** +* [Core] Performance optimizations. * [Core] Due to performance reasons the timestamp of log messages is now in UTC format. * [Core] Added several packet validations. * [Core] Log messages now contain the complete source path including parent components. @@ -35,6 +36,7 @@ * [Server] Added a new interceptor which is invoked before a new message is added to the client queue. * [Server] Added support for Linux servers by dividing IPv4 and IPv6 support and adding new options (BREAKING CHANGE!). * [Server] Gracefully closed connections are no longer reported as warnings. +* [Server] Added new overloads for initializing the ASP.NET Core integration. Copyright Christian Kratky 2016-2018 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