diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec
index 5be2f945..20489c78 100644
--- a/Build/MQTTnet.AspNetCore.nuspec
+++ b/Build/MQTTnet.AspNetCore.nuspec
@@ -10,15 +10,14 @@
https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.pngfalseThis is a support library to integrate MQTTnet into AspNetCore.
- * For more release notes please check the MQTTnet release notes.
-
+ For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).Copyright Christian Kratky 2016-2018MQTT 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 99a58c4a..572f73d3 100644
--- a/Build/MQTTnet.Extensions.ManagedClient.nuspec
+++ b/Build/MQTTnet.Extensions.ManagedClient.nuspec
@@ -10,12 +10,11 @@
https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.pngfalseThis is an extension library which provides a managed MQTT client with additional features using MQTTnet.
- * For more release notes please check the MQTTnet release notes.
-
+ For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).Copyright Christian Kratky 2016-2018MQTT 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 736f92da..29cec2b4 100644
--- a/Build/MQTTnet.Extensions.Rpc.nuspec
+++ b/Build/MQTTnet.Extensions.Rpc.nuspec
@@ -10,12 +10,11 @@
https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.pngfalseThis is an extension library which allows executing synchronous device calls including a response using MQTTnet.
- * For more release notes please check the MQTTnet release notes.
-
+ For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).Copyright Christian Kratky 2016-2018MQTT 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 b92aece6..e3a8d290 100644
--- a/Build/MQTTnet.nuspec
+++ b/Build/MQTTnet.nuspec
@@ -10,15 +10,14 @@
https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.pngfalseMQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).
- * [Core] Performance optimizations.
-* [Client] Added support for proxies when using web socket connections (thanks to PitySOFT).
-* [Client] Refactored TLS parameter usage and added more parameters (thanks to PitySOFT).
-* [Client] Fixed a deadlock when clients are disconnecting (thanks to @JanEggers).
-* [ManagedClient] Unsubscriptions are now pushed before subscriptions to support re-subscribing (thanks to @cstichlberger).
-* [Server] Fixed an issue in client keep alive checks (thanks to @jenscski).
-* [Server] Changed the order of _ClientConnected_ and _ClientDisconnected_ events so that _ClientConnected_ is fired at first (thanks to @jenscski).
-* [Server] Fixed an iussue which lets the server stop processing messages when using the application message interceptor (thanks to @alamsor).
-* [Server] Fixed a deadlock when clients are disconnecting (thanks to @JanEggers).
+ * [Core] Updated nuget packages due to security issues.
+* [Client] Fixed wrong behavior of publish method when client is disconnecting (thanks to @PaulFake).
+* [ManagedClient] Added max pending messages count option.
+* [ManagedClient] Add pending messages overflow strategy option.
+* [Server] Added new method which exposes all retained messages.
+* [Server] Removed (wrong) setter from the server options interface.
+* [Server] fixed cpu spike in case a client disconnectes (issue 421).
+* [Server] fixed concurrent sends with Aspnetcore.Connections.Abstractions based transport.
Copyright Christian Kratky 2016-2018MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin
@@ -27,7 +26,7 @@
-
+
@@ -36,7 +35,7 @@
-
+
diff --git a/README.md b/README.md
index 1a34eeed..cb405dd4 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,11 @@
+
+
[](https://www.nuget.org/packages/MQTTnet)
-[](https://ci.appveyor.com/project/chkr1011/mqttnet/branch/develop)
+[](https://ci.appveyor.com/project/chkr1011/mqttnet)
[](https://bettercodehub.com/)
[](https://opencollective.com/mqttnet)
[](https://opencollective.com/mqttnet)
@@ -23,7 +25,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* Performance optimized (processing ~70.000 messages / second)*
* Interfaces included for mocking and testing
* Access to internal trace messages
-* Unit tested (~90 tests)
+* Unit tested (~100 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/Source/MQTTnet.AspnetCore/AspNetMqttServerOptionsBuilder.cs b/Source/MQTTnet.AspnetCore/AspNetMqttServerOptionsBuilder.cs
new file mode 100644
index 00000000..e0ae3330
--- /dev/null
+++ b/Source/MQTTnet.AspnetCore/AspNetMqttServerOptionsBuilder.cs
@@ -0,0 +1,15 @@
+using MQTTnet.Server;
+using System;
+
+namespace MQTTnet.AspNetCore
+{
+ public class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
+ {
+ public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider)
+ {
+ ServiceProvider = serviceProvider;
+ }
+
+ public IServiceProvider ServiceProvider { get; }
+ }
+}
diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
index dd3a4d55..dc477f76 100644
--- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
+++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
@@ -14,9 +14,9 @@
-
-
-
+
+
+
diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
index 2d27894d..56c094c0 100644
--- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
+++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Connections;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Client.Tcp;
+using MQTTnet.Exceptions;
using MQTTnet.Packets;
using MQTTnet.Serializer;
using System;
@@ -24,6 +25,8 @@ namespace MQTTnet.AspNetCore
public event EventHandler ReadingPacketStarted;
public event EventHandler ReadingPacketCompleted;
+ private readonly SemaphoreSlim _writerSemaphore = new SemaphoreSlim(1, 1);
+
public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
if (Connection is TcpConnection tcp && !tcp.IsConnected)
@@ -57,7 +60,7 @@ namespace MQTTnet.AspNetCore
}
else
{
- readResult = await readTask;
+ readResult = await readTask.ConfigureAwait(false);
}
var buffer = readResult.Buffer;
@@ -81,7 +84,7 @@ namespace MQTTnet.AspNetCore
}
else if (readResult.IsCompleted)
{
- break;
+ throw new MqttCommunicationException("Connection Aborted");
}
}
finally
@@ -102,10 +105,20 @@ namespace MQTTnet.AspNetCore
return null;
}
- public Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
+ public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
{
- var buffer = PacketSerializerAdapter.Serialize(packet);
- return Connection.Transport.Output.WriteAsync(buffer.AsMemory(), cancellationToken).AsTask();
+ var buffer = PacketSerializer.Serialize(packet).AsMemory();
+ var output = Connection.Transport.Output;
+
+ await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+ try
+ {
+ await output.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ _writerSemaphore.Release();
+ }
}
public void Dispose()
diff --git a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
index b5d0440a..d9d9fdb8 100644
--- a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
+++ b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
@@ -20,14 +20,27 @@ namespace MQTTnet.AspNetCore
return services;
}
-
+
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action configure)
{
- var builder = new MqttServerOptionsBuilder();
+ services.AddSingleton(s => {
+ var builder = new MqttServerOptionsBuilder();
+ configure(builder);
+ return builder.Build();
+ });
- configure(builder);
-
- services.AddSingleton(builder.Build());
+ services.AddHostedMqttServer();
+
+ return services;
+ }
+
+ public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action configure)
+ {
+ services.AddSingleton(s => {
+ var builder = new AspNetMqttServerOptionsBuilder(s);
+ configure(builder);
+ return builder.Build();
+ });
services.AddHostedMqttServer();
diff --git a/Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageSkippedEventArgs.cs b/Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageSkippedEventArgs.cs
new file mode 100644
index 00000000..0f1613d4
--- /dev/null
+++ b/Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageSkippedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace MQTTnet.Extensions.ManagedClient
+{
+ public class ApplicationMessageSkippedEventArgs : EventArgs
+ {
+ public ApplicationMessageSkippedEventArgs(ManagedMqttApplicationMessage applicationMessage)
+ {
+ ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
+ }
+
+ public ManagedMqttApplicationMessage ApplicationMessage { get; }
+ }
+}
diff --git a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs
index b5a6c50b..d0035849 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs
@@ -15,6 +15,7 @@ namespace MQTTnet.Extensions.ManagedClient
event EventHandler Disconnected;
event EventHandler ApplicationMessageProcessed;
+ event EventHandler ApplicationMessageSkipped;
event EventHandler ConnectingFailed;
event EventHandler SynchronizingSubscriptionsFailed;
diff --git a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs
index 8d48babd..f59aca7c 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs
@@ -1,5 +1,6 @@
using System;
using MQTTnet.Client;
+using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
@@ -12,5 +13,9 @@ namespace MQTTnet.Extensions.ManagedClient
TimeSpan ConnectionCheckInterval { get; }
IManagedMqttClientStorage Storage { get; }
+
+ int MaxPendingMessages { get; }
+
+ MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; }
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs
index a38bcc27..271e6b93 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -7,13 +6,17 @@ using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
+using MQTTnet.Internal;
using MQTTnet.Protocol;
+using MQTTnet.Server;
+using MqttClientConnectedEventArgs = MQTTnet.Client.MqttClientConnectedEventArgs;
+using MqttClientDisconnectedEventArgs = MQTTnet.Client.MqttClientDisconnectedEventArgs;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedMqttClient : IManagedMqttClient
{
- private readonly BlockingCollection _messageQueue = new BlockingCollection();
+ private readonly BlockingQueue _messageQueue = new BlockingQueue();
private readonly Dictionary _subscriptions = new Dictionary();
private readonly HashSet _unsubscriptions = new HashSet();
@@ -50,6 +53,7 @@ namespace MQTTnet.Extensions.ManagedClient
public event EventHandler ApplicationMessageReceived;
public event EventHandler ApplicationMessageProcessed;
+ public event EventHandler ApplicationMessageSkipped;
public event EventHandler ConnectingFailed;
public event EventHandler SynchronizingSubscriptionsFailed;
@@ -75,7 +79,7 @@ namespace MQTTnet.Extensions.ManagedClient
foreach (var message in messages)
{
- _messageQueue.Add(message);
+ _messageQueue.Enqueue(message);
}
}
@@ -93,10 +97,7 @@ namespace MQTTnet.Extensions.ManagedClient
StopPublishing();
StopMaintainingConnection();
- while (_messageQueue.Any())
- {
- _messageQueue.Take();
- }
+ _messageQueue.Clear();
return Task.FromResult(0);
}
@@ -112,12 +113,38 @@ namespace MQTTnet.Extensions.ManagedClient
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
- if (_storageManager != null)
+ ManagedMqttApplicationMessage removedMessage = null;
+ lock (_messageQueue)
{
- await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
+ if (_messageQueue.Count >= _options.MaxPendingMessages)
+ {
+ if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
+ {
+ _logger.Verbose("Skipping publish of new application message because internal queue is full.");
+ ApplicationMessageSkipped?.Invoke(this, new ApplicationMessageSkippedEventArgs(applicationMessage));
+ return;
+ }
+
+ if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
+ {
+ removedMessage = _messageQueue.RemoveFirst();
+ _logger.Verbose("Removed oldest application message from internal queue because it is full.");
+ ApplicationMessageSkipped?.Invoke(this, new ApplicationMessageSkippedEventArgs(removedMessage));
+ }
+ }
+
+ _messageQueue.Enqueue(applicationMessage);
}
- _messageQueue.Add(applicationMessage);
+ if (_storageManager != null)
+ {
+ if (removedMessage != null)
+ {
+ await _storageManager.RemoveAsync(removedMessage).ConfigureAwait(false);
+ }
+
+ await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
+ }
}
public Task SubscribeAsync(IEnumerable topicFilters)
@@ -157,7 +184,6 @@ namespace MQTTnet.Extensions.ManagedClient
public void Dispose()
{
- _messageQueue?.Dispose();
_connectionCancellationToken?.Dispose();
_publishingCancellationToken?.Dispose();
}
@@ -226,9 +252,9 @@ namespace MQTTnet.Extensions.ManagedClient
{
try
{
- while (!cancellationToken.IsCancellationRequested)
+ while (!cancellationToken.IsCancellationRequested && _mqttClient.IsConnected)
{
- var message = _messageQueue.Take(cancellationToken);
+ var message = _messageQueue.Dequeue();
if (message == null)
{
continue;
@@ -268,7 +294,7 @@ namespace MQTTnet.Extensions.ManagedClient
if (message.ApplicationMessage.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
- _messageQueue.Add(message);
+ _messageQueue.Enqueue(message);
}
}
catch (Exception exception)
@@ -367,7 +393,6 @@ namespace MQTTnet.Extensions.ManagedClient
}
var cts = new CancellationTokenSource();
-
_publishingCancellationToken = cts;
Task.Factory.StartNew(() => PublishQueuedMessages(cts.Token), cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs
index cecf2274..cba801bb 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs
@@ -1,5 +1,6 @@
using System;
using MQTTnet.Client;
+using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
@@ -12,5 +13,9 @@ namespace MQTTnet.Extensions.ManagedClient
public TimeSpan ConnectionCheckInterval { get; set; } = TimeSpan.FromSeconds(1);
public IManagedMqttClientStorage Storage { get; set; }
+
+ public int MaxPendingMessages { get; set; } = int.MaxValue;
+
+ public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropNewMessage;
}
}
diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs
index ef774c59..3ea5b050 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs
@@ -1,5 +1,6 @@
using System;
using MQTTnet.Client;
+using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
@@ -8,6 +9,18 @@ namespace MQTTnet.Extensions.ManagedClient
private readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
private MqttClientOptionsBuilder _clientOptionsBuilder;
+ public ManagedMqttClientOptionsBuilder WithMaxPendingMessages(int value)
+ {
+ _options.MaxPendingMessages = value;
+ return this;
+ }
+
+ public ManagedMqttClientOptionsBuilder WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy value)
+ {
+ _options.PendingMessagesOverflowStrategy = value;
+ return this;
+ }
+
public ManagedMqttClientOptionsBuilder WithAutoReconnectDelay(TimeSpan value)
{
_options.AutoReconnectDelay = value;
diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs
index 0f4e33e3..821a7d92 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs
@@ -28,6 +28,8 @@ namespace MQTTnet.Extensions.ManagedClient
public async Task AddAsync(ManagedMqttApplicationMessage applicationMessage)
{
+ if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
+
using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
{
_messages.Add(applicationMessage);
@@ -37,6 +39,8 @@ namespace MQTTnet.Extensions.ManagedClient
public async Task RemoveAsync(ManagedMqttApplicationMessage applicationMessage)
{
+ if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
+
using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
{
var index = _messages.IndexOf(applicationMessage);
diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs
index 01d252b0..dc95152f 100644
--- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs
+++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs
@@ -154,6 +154,9 @@ namespace MQTTnet.Adapter
return packet;
}
+ catch (OperationCanceledException)
+ {
+ }
catch (Exception exception)
{
if (IsWrappedException(exception))
@@ -241,7 +244,8 @@ namespace MQTTnet.Adapter
{
if (exception is IOException && exception.InnerException is SocketException socketException)
{
- if (socketException.SocketErrorCode == SocketError.ConnectionAborted)
+ if (socketException.SocketErrorCode == SocketError.ConnectionAborted ||
+ socketException.SocketErrorCode == SocketError.OperationAborted)
{
throw new OperationCanceledException();
}
diff --git a/Source/MQTTnet/Client/IMqttClientFactory.cs b/Source/MQTTnet/Client/IMqttClientFactory.cs
index a15b499b..cd687afc 100644
--- a/Source/MQTTnet/Client/IMqttClientFactory.cs
+++ b/Source/MQTTnet/Client/IMqttClientFactory.cs
@@ -7,5 +7,9 @@ namespace MQTTnet.Client
IMqttClient CreateMqttClient();
IMqttClient CreateMqttClient(IMqttNetLogger logger);
+
+ IMqttClient CreateMqttClient(IMqttClientAdapterFactory adapterFactory);
+
+ IMqttClient CreateMqttClient(IMqttNetLogger logger, IMqttClientAdapterFactory adapterFactory);
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs
index 643d0ef9..ce91a487 100644
--- a/Source/MQTTnet/Client/MqttClient.cs
+++ b/Source/MQTTnet/Client/MqttClient.cs
@@ -30,7 +30,7 @@ namespace MQTTnet.Client
internal Task _keepAliveMessageSenderTask;
private IMqttChannelAdapter _adapter;
private bool _cleanDisconnectInitiated;
- private TaskCompletionSource _disconnectReason;
+ private long _disconnectGate;
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
{
@@ -55,12 +55,12 @@ namespace MQTTnet.Client
try
{
- _cancellationTokenSource = new CancellationTokenSource();
- _disconnectReason = new TaskCompletionSource();
_options = options;
_packetIdentifierProvider.Reset();
_packetDispatcher.Reset();
+ _cancellationTokenSource = new CancellationTokenSource();
+ _disconnectGate = 0;
_adapter = _adapterFactory.CreateClientAdapter(options, _logger);
_logger.Verbose($"Trying to connect with server ({_options.ChannelOptions}).");
@@ -88,10 +88,12 @@ namespace MQTTnet.Client
catch (Exception exception)
{
_logger.Error(exception, "Error while connecting with server.");
- if (_disconnectReason.TrySetException(exception))
+
+ if (!DisconnectIsPending())
{
await DisconnectInternalAsync(null, exception).ConfigureAwait(false);
}
+
throw;
}
}
@@ -109,7 +111,7 @@ namespace MQTTnet.Client
}
finally
{
- if (_disconnectReason.TrySetCanceled())
+ if (!DisconnectIsPending())
{
await DisconnectInternalAsync(null, null).ConfigureAwait(false);
}
@@ -184,10 +186,12 @@ namespace MQTTnet.Client
public void Dispose()
{
+ _cancellationTokenSource?.Cancel(false);
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_adapter?.Dispose();
+ _adapter = null;
}
private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken)
@@ -213,7 +217,7 @@ namespace MQTTnet.Client
private void ThrowIfNotConnected()
{
- if (!IsConnected) throw new MqttCommunicationException("The client is not connected.");
+ if (!IsConnected || Interlocked.Read(ref _disconnectGate) == 1) throw new MqttCommunicationException("The client is not connected.");
}
private void ThrowIfConnected(string message)
@@ -223,9 +227,10 @@ namespace MQTTnet.Client
private async Task DisconnectInternalAsync(Task sender, Exception exception)
{
- InitiateDisconnect();
-
var clientWasConnected = IsConnected;
+
+ InitiateDisconnect();
+
IsConnected = false;
try
@@ -246,10 +251,7 @@ namespace MQTTnet.Client
}
finally
{
- _adapter?.Dispose();
- _adapter = null;
- _cancellationTokenSource?.Dispose();
- _cancellationTokenSource = null;
+ Dispose();
_cleanDisconnectInitiated = false;
_logger.Info("Disconnected.");
@@ -263,16 +265,16 @@ namespace MQTTnet.Client
{
try
{
- if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested)
+ if (_cancellationTokenSource?.IsCancellationRequested == true)
{
return;
}
- _cancellationTokenSource.Cancel(false);
+ _cancellationTokenSource?.Cancel(false);
}
- catch (Exception adapterException)
+ catch (Exception exception)
{
- _logger.Warning(adapterException, "Error while initiating disconnect.");
+ _logger.Warning(exception, "Error while initiating disconnect.");
}
}
}
@@ -360,7 +362,7 @@ namespace MQTTnet.Client
_logger.Error(exception, "Unhandled exception while sending/receiving keep alive packets.");
}
- if (_disconnectReason.TrySetException(exception))
+ if (!DisconnectIsPending())
{
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
}
@@ -379,8 +381,10 @@ namespace MQTTnet.Client
{
while (!cancellationToken.IsCancellationRequested)
{
- var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
- if (packet != null)
+ var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken)
+ .ConfigureAwait(false);
+
+ if (packet != null && !cancellationToken.IsCancellationRequested)
{
await ProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
}
@@ -407,7 +411,7 @@ namespace MQTTnet.Client
_packetDispatcher.Dispatch(exception);
- if (_disconnectReason.TrySetException(exception))
+ if (!DisconnectIsPending())
{
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
}
@@ -493,16 +497,20 @@ namespace MQTTnet.Client
private void StartReceivingPackets(CancellationToken cancellationToken)
{
- _packetReceiverTask = Task.Run(
+ _packetReceiverTask = Task.Factory.StartNew(
() => ReceivePacketsAsync(cancellationToken),
- cancellationToken);
+ cancellationToken,
+ TaskCreationOptions.LongRunning,
+ TaskScheduler.Default);
}
private void StartSendingKeepAliveMessages(CancellationToken cancellationToken)
{
- _keepAliveMessageSenderTask = Task.Run(
+ _keepAliveMessageSenderTask = Task.Factory.StartNew(
() => SendKeepAliveMessagesAsync(cancellationToken),
- cancellationToken);
+ cancellationToken,
+ TaskCreationOptions.LongRunning,
+ TaskScheduler.Default);
}
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
@@ -538,5 +546,10 @@ namespace MQTTnet.Client
{
}
}
+
+ private bool DisconnectIsPending()
+ {
+ return Interlocked.CompareExchange(ref _disconnectGate, 1, 0) != 0;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs
index bc10d65c..39283288 100644
--- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs
+++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs
@@ -116,7 +116,7 @@ namespace MQTTnet.Implementations
return true;
}
- if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.RevocationStatusUnknown))
+ if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.OfflineRevocation))
{
if (!_options.TlsOptions.IgnoreCertificateRevocationErrors)
{
diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs
index d91b66e6..59da016a 100644
--- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs
+++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs
@@ -20,8 +20,8 @@ namespace MQTTnet.Implementations
private readonly CancellationToken _cancellationToken;
private readonly AddressFamily _addressFamily;
private readonly MqttServerTcpEndpointBaseOptions _options;
+ private readonly MqttServerTlsTcpEndpointOptions _tlsOptions;
private readonly X509Certificate2 _tlsCertificate;
-
private Socket _socket;
public MqttTcpServerListener(
@@ -36,6 +36,11 @@ namespace MQTTnet.Implementations
_tlsCertificate = tlsCertificate;
_cancellationToken = cancellationToken;
_logger = logger.CreateChildLogger(nameof(MqttTcpServerListener));
+
+ if (_options is MqttServerTlsTcpEndpointOptions tlsOptions)
+ {
+ _tlsOptions = tlsOptions;
+ }
}
public event EventHandler ClientAccepted;
@@ -75,7 +80,7 @@ namespace MQTTnet.Implementations
if (_tlsCertificate != null)
{
sslStream = new SslStream(new NetworkStream(clientSocket), false);
- await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
+ await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, _tlsOptions.SslProtocol, false).ConfigureAwait(false);
}
_logger.Verbose("Client '{0}' accepted by TCP listener '{1}, {2}'.",
diff --git a/Source/MQTTnet/Internal/BlockingQueue.cs b/Source/MQTTnet/Internal/BlockingQueue.cs
new file mode 100644
index 00000000..d6c28f46
--- /dev/null
+++ b/Source/MQTTnet/Internal/BlockingQueue.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace MQTTnet.Internal
+{
+ public class BlockingQueue
+ {
+ private readonly object _syncRoot = new object();
+ private readonly LinkedList _items = new LinkedList();
+ private readonly ManualResetEvent _gate = new ManualResetEvent(false);
+
+ public int Count
+ {
+ get
+ {
+ lock (_syncRoot)
+ {
+ return _items.Count;
+ }
+ }
+ }
+
+ public void Enqueue(TItem item)
+ {
+ if (item == null) throw new ArgumentNullException(nameof(item));
+
+ lock (_syncRoot)
+ {
+ _items.AddLast(item);
+ _gate.Set();
+ }
+ }
+
+ public TItem Dequeue()
+ {
+ while (true)
+ {
+ lock (_syncRoot)
+ {
+ if (_items.Count > 0)
+ {
+ var item = _items.First.Value;
+ _items.RemoveFirst();
+
+ return item;
+ }
+
+ if (_items.Count == 0)
+ {
+ _gate.Reset();
+ }
+ }
+
+ _gate.WaitOne();
+ }
+ }
+
+ public TItem RemoveFirst()
+ {
+ lock (_syncRoot)
+ {
+ var item = _items.First;
+ _items.RemoveFirst();
+
+ return item.Value;
+ }
+ }
+
+ public void Clear()
+ {
+ lock (_syncRoot)
+ {
+ _items.Clear();
+ }
+ }
+ }
+}
diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj
index 1704d7d0..5c9ba727 100644
--- a/Source/MQTTnet/MQTTnet.csproj
+++ b/Source/MQTTnet/MQTTnet.csproj
@@ -54,7 +54,7 @@
-
+
diff --git a/Source/MQTTnet/Server/IMqttServer.cs b/Source/MQTTnet/Server/IMqttServer.cs
index e724a65f..3a45973b 100644
--- a/Source/MQTTnet/Server/IMqttServer.cs
+++ b/Source/MQTTnet/Server/IMqttServer.cs
@@ -16,8 +16,14 @@ namespace MQTTnet.Server
IMqttServerOptions Options { get; }
+ [Obsolete("This method is no longer async. Use the not async method.")]
Task> GetClientSessionsStatusAsync();
+ IList GetClientSessionsStatus();
+
+ IList GetRetainedMessages();
+ Task ClearRetainedMessagesAsync();
+
Task SubscribeAsync(string clientId, IList topicFilters);
Task UnsubscribeAsync(string clientId, IList topicFilters);
diff --git a/Source/MQTTnet/Server/IMqttServerFactory.cs b/Source/MQTTnet/Server/IMqttServerFactory.cs
index 6e399cae..7d23ed36 100644
--- a/Source/MQTTnet/Server/IMqttServerFactory.cs
+++ b/Source/MQTTnet/Server/IMqttServerFactory.cs
@@ -8,6 +8,8 @@ namespace MQTTnet.Server
{
IMqttServer CreateMqttServer();
+ IMqttServer CreateMqttServer(IMqttNetLogger logger);
+
IMqttServer CreateMqttServer(IEnumerable adapters, IMqttNetLogger logger);
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Server/IMqttServerOptions.cs b/Source/MQTTnet/Server/IMqttServerOptions.cs
index 2ae4c9fa..f9334fd7 100644
--- a/Source/MQTTnet/Server/IMqttServerOptions.cs
+++ b/Source/MQTTnet/Server/IMqttServerOptions.cs
@@ -14,7 +14,7 @@ namespace MQTTnet.Server
Action ConnectionValidator { get; }
Action SubscriptionInterceptor { get; }
Action ApplicationMessageInterceptor { get; }
- Action ClientMessageQueueInterceptor { get; set; }
+ Action ClientMessageQueueInterceptor { get; }
MqttServerTcpEndpointOptions DefaultEndpointOptions { get; }
MqttServerTlsTcpEndpointOptions TlsEndpointOptions { get; }
diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs
index d5448e80..1005110b 100644
--- a/Source/MQTTnet/Server/MqttClientSession.cs
+++ b/Source/MQTTnet/Server/MqttClientSession.cs
@@ -137,7 +137,9 @@ namespace MQTTnet.Server
_cleanupHandle?.Dispose();
_cleanupHandle = null;
-
+
+ _adapter = null;
+ _cancellationTokenSource?.Cancel(false);
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
@@ -289,7 +291,9 @@ namespace MQTTnet.Server
{
_pendingPacketsQueue?.Dispose();
+ _cancellationTokenSource?.Cancel ();
_cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = null;
}
private void ProcessReceivedPacket(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken)
diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs
index a6a6b9df..f180f685 100644
--- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs
+++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs
@@ -66,7 +66,7 @@ namespace MQTTnet.Server
return Task.Run(() => RunSession(clientAdapter, _cancellationToken), _cancellationToken);
}
- public Task> GetClientStatusAsync()
+ public IList GetClientStatus()
{
var result = new List();
@@ -78,7 +78,7 @@ namespace MQTTnet.Server
result.Add(status);
}
- return Task.FromResult((IList)result);
+ return result;
}
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
diff --git a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs
index 86c321fc..bf5de984 100644
--- a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs
+++ b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs
@@ -52,7 +52,51 @@ namespace MQTTnet.Server
try
{
- await HandleMessageInternalAsync(clientId, applicationMessage).ConfigureAwait(false);
+ List messagesForSave = null;
+ lock (_messages)
+ {
+ var saveIsRequired = false;
+
+ if (applicationMessage.Payload?.Length == 0)
+ {
+ saveIsRequired = _messages.Remove(applicationMessage.Topic);
+ _logger.Verbose("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic);
+ }
+ else
+ {
+ if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage))
+ {
+ _messages[applicationMessage.Topic] = applicationMessage;
+ saveIsRequired = true;
+ }
+ else
+ {
+ if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0]))
+ {
+ _messages[applicationMessage.Topic] = applicationMessage;
+ saveIsRequired = true;
+ }
+ }
+
+ _logger.Verbose("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic);
+ }
+
+ if (saveIsRequired)
+ {
+ messagesForSave = new List(_messages.Values);
+ }
+ }
+
+ if (messagesForSave == null)
+ {
+ _logger.Verbose("Skipped saving retained messages because no changes were detected.");
+ return;
+ }
+
+ if (_options.Storage != null)
+ {
+ await _options.Storage.SaveRetainedMessagesAsync(messagesForSave).ConfigureAwait(false);
+ }
}
catch (Exception exception)
{
@@ -84,52 +128,27 @@ namespace MQTTnet.Server
return retainedMessages;
}
- private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage)
+ public IList GetMessages()
{
- var saveIsRequired = false;
-
lock (_messages)
{
- if (applicationMessage.Payload?.Length == 0)
- {
- saveIsRequired = _messages.Remove(applicationMessage.Topic);
- _logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic);
- }
- else
- {
- if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage))
- {
- _messages[applicationMessage.Topic] = applicationMessage;
- saveIsRequired = 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);
- }
+ return _messages.Values.ToList();
}
+ }
- if (!saveIsRequired)
+ public Task ClearMessagesAsync()
+ {
+ lock (_messages)
{
- _logger.Verbose("Skipped saving retained messages because no changes were detected.");
+ _messages.Clear();
}
- if (saveIsRequired && _options.Storage != null)
+ if (_options.Storage != null)
{
- List messages;
- lock (_messages)
- {
- messages = _messages.Values.ToList();
- }
-
- await _options.Storage.SaveRetainedMessagesAsync(messages).ConfigureAwait(false);
+ return _options.Storage.SaveRetainedMessagesAsync(new List());
}
+
+ return Task.FromResult((object)null);
}
}
}
diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs
index 6d532917..a960ab00 100644
--- a/Source/MQTTnet/Server/MqttServer.cs
+++ b/Source/MQTTnet/Server/MqttServer.cs
@@ -41,7 +41,17 @@ namespace MQTTnet.Server
public Task> GetClientSessionsStatusAsync()
{
- return _clientSessionsManager.GetClientStatusAsync();
+ return Task.FromResult(_clientSessionsManager.GetClientStatus());
+ }
+
+ public IList GetClientSessionsStatus()
+ {
+ return _clientSessionsManager.GetClientStatus();
+ }
+
+ public IList GetRetainedMessages()
+ {
+ return _retainedMessagesManager.GetMessages();
}
public Task SubscribeAsync(string clientId, IList topicFilters)
@@ -129,6 +139,11 @@ namespace MQTTnet.Server
}
}
+ public Task ClearRetainedMessagesAsync()
+ {
+ return _retainedMessagesManager?.ClearMessagesAsync();
+ }
+
internal void OnClientConnected(string clientId)
{
_logger.Info("Client '{0}': Connected.", clientId);
diff --git a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs
index f54f379f..45400620 100644
--- a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs
+++ b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs
@@ -1,5 +1,6 @@
using System;
using System.Net;
+using System.Security.Authentication;
namespace MQTTnet.Server
{
@@ -80,6 +81,12 @@ namespace MQTTnet.Server
return this;
}
+ public MqttServerOptionsBuilder WithEncryptionSslProtocol(SslProtocols value)
+ {
+ _options.TlsEndpointOptions.SslProtocol = value;
+ return this;
+ }
+
public MqttServerOptionsBuilder WithoutEncryptedEndpoint()
{
_options.TlsEndpointOptions.IsEnabled = false;
diff --git a/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs b/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs
index 76d6c862..88d2f86d 100644
--- a/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs
+++ b/Source/MQTTnet/Server/MqttServerTlsTcpEndpointOptions.cs
@@ -1,4 +1,6 @@
-namespace MQTTnet.Server
+using System.Security.Authentication;
+
+namespace MQTTnet.Server
{
public class MqttServerTlsTcpEndpointOptions : MqttServerTcpEndpointBaseOptions
{
@@ -8,5 +10,8 @@
}
public byte[] Certificate { get; set; }
+
+
+ public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
}
}
diff --git a/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj b/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj
index 77dfff97..998acd2f 100644
--- a/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj
+++ b/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj
@@ -1,4 +1,4 @@
-
+netcoreapp2.1
@@ -7,9 +7,9 @@
-
-
-
+
+
+
diff --git a/Tests/MQTTnet.AspNetCore.Tests/Mockups/ConnectionContextMockup.cs b/Tests/MQTTnet.AspNetCore.Tests/Mockups/ConnectionContextMockup.cs
new file mode 100644
index 00000000..296959ef
--- /dev/null
+++ b/Tests/MQTTnet.AspNetCore.Tests/Mockups/ConnectionContextMockup.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using System.IO.Pipelines;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace MQTTnet.AspNetCore.Tests.Mockups
+{
+ public class ConnectionContextMockup : ConnectionContext
+ {
+ public override string ConnectionId { get; set; }
+
+ public override IFeatureCollection Features { get; }
+
+ public override IDictionary