diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec
index 32bb327f..5e0bce99 100644
--- a/Build/MQTTnet.nuspec
+++ b/Build/MQTTnet.nuspec
@@ -13,6 +13,10 @@
MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.
* [ManagedClient] Extended ReconnectAsync (thanks to @nvsnkv, #1202).
+* [Server] Fixed a memory/performance leak when using QoS Level 1.
+* [Server] Exposed connection timestamp in client status.
+* [Server] Refactored connection management code.
+* [Server] Exposed more details in _MqttServerClientConnectedEventArgs_.
* [MQTTnet.Server] Moved server project to a dedicated GitHub repository.
* [MQTTnet, MQTTnet.Extensions.ManagedClient] Fixed bug that allowed invalid subscriptions (Thanks to @marcelwinh).
Git commit: $gitCommit
diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs
index 8163fc01..2e8bf91f 100644
--- a/Source/MQTTnet/Client/MqttClient.cs
+++ b/Source/MQTTnet/Client/MqttClient.cs
@@ -363,9 +363,9 @@ namespace MQTTnet.Client
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
}
- catch (Exception e)
+ catch (Exception innerException)
{
- _logger.Warning(e, "Error while waiting for internal tasks.");
+ _logger.Warning(innerException, "Error while waiting for internal tasks.");
}
finally
{
@@ -439,7 +439,7 @@ namespace MQTTnet.Client
{
if (exception is MqttCommunicationTimedOutException)
{
- _logger.Warning(null, "Timeout while waiting for response packet ({0}).", typeof(TResponsePacket).Name);
+ _logger.Warning("Timeout while waiting for response packet ({0}).", typeof(TResponsePacket).Name);
}
throw;
diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
index 5bd4eac1..a8560b36 100644
--- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
@@ -133,7 +133,7 @@ namespace MQTTnet.Client.Options
return this;
}
- public MqttClientOptionsBuilder WithCredentials(string username, string password = null)
+ public MqttClientOptionsBuilder WithCredentials(string username, string password)
{
byte[] passwordBuffer = null;
@@ -145,7 +145,7 @@ namespace MQTTnet.Client.Options
return WithCredentials(username, passwordBuffer);
}
- public MqttClientOptionsBuilder WithCredentials(string username, byte[] password = null)
+ public MqttClientOptionsBuilder WithCredentials(string username, byte[] password)
{
_options.Credentials = new MqttClientCredentials
{
@@ -155,6 +155,16 @@ namespace MQTTnet.Client.Options
return this;
}
+
+ public MqttClientOptionsBuilder WithCredentials(string username)
+ {
+ _options.Credentials = new MqttClientCredentials
+ {
+ Username = username
+ };
+
+ return this;
+ }
public MqttClientOptionsBuilder WithCredentials(IMqttClientCredentials credentials)
{
diff --git a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs
index 1d73aaa1..dd25bb5c 100644
--- a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs
+++ b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs
@@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
+using MQTTnet.Implementations;
namespace MQTTnet.Client.Receiving
{
- public class MqttApplicationMessageReceivedHandlerDelegate : IMqttApplicationMessageReceivedHandler
+ public sealed class MqttApplicationMessageReceivedHandlerDelegate : IMqttApplicationMessageReceivedHandler
{
- private readonly Func _handler;
+ readonly Func _handler;
public MqttApplicationMessageReceivedHandlerDelegate(Action handler)
{
@@ -14,7 +15,7 @@ namespace MQTTnet.Client.Receiving
_handler = context =>
{
handler(context);
- return Task.FromResult(0);
+ return PlatformAbstractionLayer.CompletedTask;
};
}
diff --git a/Source/MQTTnet/Formatter/IMqttDataConverter.cs b/Source/MQTTnet/Formatter/IMqttDataConverter.cs
index aec47027..71fc30be 100644
--- a/Source/MQTTnet/Formatter/IMqttDataConverter.cs
+++ b/Source/MQTTnet/Formatter/IMqttDataConverter.cs
@@ -8,7 +8,6 @@ using MQTTnet.Client.Unsubscribing;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
-using MqttClientSubscribeResult = MQTTnet.Client.Subscribing.MqttClientSubscribeResult;
namespace MQTTnet.Formatter
{
@@ -22,7 +21,7 @@ namespace MQTTnet.Formatter
MqttClientPublishResult CreateClientPublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket);
- MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket);
+ Client.Subscribing.MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket);
MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket);
diff --git a/Source/MQTTnet/MqttTopicFilter.cs b/Source/MQTTnet/MqttTopicFilter.cs
index fda60e0a..e9cea55a 100644
--- a/Source/MQTTnet/MqttTopicFilter.cs
+++ b/Source/MQTTnet/MqttTopicFilter.cs
@@ -8,6 +8,7 @@ namespace MQTTnet
{
}
+ // TODO: Consider using struct instead.
public class MqttTopicFilter
{
///
diff --git a/Source/MQTTnet/Protocol/MqttRetainHandling.cs b/Source/MQTTnet/Protocol/MqttRetainHandling.cs
index 8ee50ccd..4f3fb465 100644
--- a/Source/MQTTnet/Protocol/MqttRetainHandling.cs
+++ b/Source/MQTTnet/Protocol/MqttRetainHandling.cs
@@ -3,7 +3,9 @@
public enum MqttRetainHandling
{
SendAtSubscribe = 0,
+
SendAtSubscribeIfNewSubscriptionOnly = 1,
+
DoNotSendOnSubscribe = 2
}
}
diff --git a/Source/MQTTnet/Server/CheckSubscriptionsResult.cs b/Source/MQTTnet/Server/Internal/CheckSubscriptionsResult.cs
similarity index 96%
rename from Source/MQTTnet/Server/CheckSubscriptionsResult.cs
rename to Source/MQTTnet/Server/Internal/CheckSubscriptionsResult.cs
index eb66f17e..18a02fd3 100644
--- a/Source/MQTTnet/Server/CheckSubscriptionsResult.cs
+++ b/Source/MQTTnet/Server/Internal/CheckSubscriptionsResult.cs
@@ -1,6 +1,6 @@
using MQTTnet.Protocol;
-namespace MQTTnet.Server
+namespace MQTTnet.Server.Internal
{
public struct CheckSubscriptionsResult
{
diff --git a/Source/MQTTnet/Server/MqttClientConnection.cs b/Source/MQTTnet/Server/Internal/MqttClientConnection.cs
similarity index 58%
rename from Source/MQTTnet/Server/MqttClientConnection.cs
rename to Source/MQTTnet/Server/Internal/MqttClientConnection.cs
index 8f33bb8b..866624a0 100644
--- a/Source/MQTTnet/Server/MqttClientConnection.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClientConnection.cs
@@ -1,94 +1,82 @@
-using MQTTnet.Adapter;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MQTTnet.Adapter;
using MQTTnet.Client;
+using MQTTnet.Client.Disconnecting;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
-using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.PacketDispatcher;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server.Status;
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using MQTTnet.Client.Disconnecting;
-namespace MQTTnet.Server
+namespace MQTTnet.Server.Internal
{
public sealed class MqttClientConnection : IDisposable
{
readonly Dictionary _topicAlias = new Dictionary();
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
- readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource();
-
+
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
readonly MqttClientSessionsManager _sessionsManager;
+ readonly IMqttChannelAdapter _channelAdapter;
readonly IMqttNetScopedLogger _logger;
readonly IMqttServerOptions _serverOptions;
- readonly IMqttChannelAdapter _channelAdapter;
- readonly MqttConnectionValidatorContext _connectionValidatorContext;
- readonly IMqttDataConverter _dataConverter;
readonly string _endpoint;
- readonly DateTime _connectedTimestamp;
- volatile Task _packageReceiverTask;
- DateTime _lastNonKeepAlivePacketReceivedTimestamp;
+ CancellationTokenSource _cancellationToken;
- long _receivedPacketsCount;
- long _sentPacketsCount = 1; // Start with 1 because the CONNECT packet is not counted anywhere.
- long _receivedApplicationMessagesCount;
- long _sentApplicationMessagesCount;
- MqttClientDisconnectReason _disconnectReason;
-
- public MqttClientConnection(MqttConnectPacket connectPacket,
+ public MqttClientConnection(
+ MqttConnectPacket connectPacket,
IMqttChannelAdapter channelAdapter,
MqttClientSession session,
- MqttConnectionValidatorContext connectionValidatorContext,
IMqttServerOptions serverOptions,
MqttClientSessionsManager sessionsManager,
IMqttRetainedMessagesManager retainedMessagesManager,
IMqttNetLogger logger)
{
- Session = session ?? throw new ArgumentNullException(nameof(session));
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
- _connectionValidatorContext = connectionValidatorContext ?? throw new ArgumentNullException(nameof(connectionValidatorContext));
- _dataConverter = _channelAdapter.PacketFormatterAdapter.DataConverter;
- _endpoint = _channelAdapter.Endpoint;
+ _endpoint = channelAdapter.Endpoint;
+
+ Session = session ?? throw new ArgumentNullException(nameof(session));
ConnectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.CreateScopedLogger(nameof(MqttClientConnection));
-
- _connectedTimestamp = DateTime.UtcNow;
- LastPacketReceivedTimestamp = _connectedTimestamp;
- _lastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp;
}
- public MqttClientConnectionStatus Status { get; private set; } = MqttClientConnectionStatus.Initializing;
-
- public MqttConnectPacket ConnectPacket { get; }
-
public string ClientId => ConnectPacket.ClientId;
- public bool IsReadingPacket => _channelAdapter.IsReadingPacket;
+ public string Endpoint => _endpoint;
- public DateTime LastPacketReceivedTimestamp { get; private set; }
+ public MqttClientConnectionStatistics Statistics { get; } = new MqttClientConnectionStatistics();
+
+ public bool IsRunning { get; private set; }
+
+ public MqttConnectPacket ConnectPacket { get; }
+
+ public bool IsReadingPacket => _channelAdapter.IsReadingPacket;
public MqttClientSession Session { get; }
+ public bool IsTakenOver { get; set; }
+
+ public bool IsCleanDisconnect { get; private set; }
+
public async Task StopAsync(MqttClientDisconnectReason reason)
{
- Status = MqttClientConnectionStatus.Finalizing;
- _disconnectReason = reason;
+ IsRunning = false;
if (reason == MqttClientDisconnectReason.SessionTakenOver || reason == MqttClientDisconnectReason.KeepAliveTimeout)
{
@@ -96,30 +84,10 @@ namespace MQTTnet.Server
// token because the entire connection is closed (disposed) as soon as the cancellation
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
// at the client!
- try
- {
- var disconnectOptions = new MqttClientDisconnectOptions
- {
- ReasonCode = reason,
- ReasonString = reason.ToString()
- };
-
- var disconnectPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateDisconnectPacket(disconnectOptions);
-
- using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
- {
- await _channelAdapter.SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
- }
- }
- catch (Exception exception)
- {
- _logger.Warning(exception, "Client '{0}': Error while sending DISCONNECT packet after takeover.", ClientId);
- }
+ await TrySendDisconnectPacket(reason).ConfigureAwait(false);
}
StopInternal();
-
- await (_packageReceiverTask ?? PlatformAbstractionLayer.CompletedTask);
}
public void ResetStatistics()
@@ -127,24 +95,16 @@ namespace MQTTnet.Server
_channelAdapter.ResetStatistics();
}
- public void FillStatus(MqttClientStatus status)
+ public void FillClientStatus(MqttClientStatus clientStatus)
{
- status.ClientId = ClientId;
- status.Endpoint = _endpoint;
- status.ProtocolVersion = _channelAdapter.PacketFormatterAdapter.ProtocolVersion;
+ clientStatus.ClientId = ClientId;
+ clientStatus.Endpoint = _endpoint;
- status.ReceivedApplicationMessagesCount = Interlocked.Read(ref _receivedApplicationMessagesCount);
- status.SentApplicationMessagesCount = Interlocked.Read(ref _sentApplicationMessagesCount);
+ clientStatus.ProtocolVersion = _channelAdapter.PacketFormatterAdapter.ProtocolVersion;
+ clientStatus.BytesSent = _channelAdapter.BytesSent;
+ clientStatus.BytesReceived = _channelAdapter.BytesReceived;
- status.ReceivedPacketsCount = Interlocked.Read(ref _receivedPacketsCount);
- status.SentPacketsCount = Interlocked.Read(ref _sentPacketsCount);
-
- status.ConnectedTimestamp = _connectedTimestamp;
- status.LastPacketReceivedTimestamp = LastPacketReceivedTimestamp;
- status.LastNonKeepAlivePacketReceivedTimestamp = _lastNonKeepAlivePacketReceivedTimestamp;
-
- status.BytesSent = _channelAdapter.BytesSent;
- status.BytesReceived = _channelAdapter.BytesReceived;
+ Statistics.FillClientStatus(clientStatus);
}
public void Dispose()
@@ -152,31 +112,60 @@ namespace MQTTnet.Server
_cancellationToken.Dispose();
}
- public Task RunAsync()
+ public async Task RunAsync()
{
- _packageReceiverTask = RunInternalAsync(_cancellationToken.Token);
- return _packageReceiverTask;
+ _logger.Info("Client '{0}': Session started.", ClientId);
+
+ Session.WillMessage = ConnectPacket.WillMessage;
+
+ using (var cancellationToken = new CancellationTokenSource())
+ {
+ _cancellationToken = cancellationToken;
+
+ try
+ {
+ Task.Run(() => SendPacketsLoop(cancellationToken.Token), cancellationToken.Token).RunInBackground(_logger);
+
+ Session.IsCleanSession = false;
+
+ IsRunning = true;
+
+ await ReceivePackagesLoop(cancellationToken.Token).ConfigureAwait(false);
+ }
+ finally
+ {
+ IsRunning = false;
+
+ cancellationToken.Cancel();
+ _cancellationToken = null;
+ }
+ }
+
+ _packetDispatcher.CancelAll();
+
+ if (!IsTakenOver && !IsCleanDisconnect && Session.WillMessage != null)
+ {
+ _sessionsManager.DispatchApplicationMessage(Session.WillMessage, this);
+ Session.WillMessage = null;
+ }
+
+ _logger.Info("Client '{0}': Connection stopped.", ClientId);
}
- async Task RunInternalAsync(CancellationToken cancellationToken)
+ Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
+ {
+ return _channelAdapter.SendPacketAsync(packet, cancellationToken).ContinueWith(task => { Statistics.HandleSentPacket(packet); }, cancellationToken);
+ }
+
+ async Task ReceivePackagesLoop(CancellationToken cancellationToken)
{
- var disconnectType = MqttClientDisconnectType.NotClean;
try
{
- _logger.Info("Client '{0}': Session started.", ClientId);
-
- Session.WillMessage = ConnectPacket.WillMessage;
-
- await SendAsync(_channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(_connectionValidatorContext), cancellationToken).ConfigureAwait(false);
-
- Task.Run(() => SendPendingPacketsAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
-
- Session.IsCleanSession = false;
-
+ // We do not listen for the cancellation token here because the internal buffer might still
+ // contain data to be read even if the TCP connection was already dropped. So we rely on an
+ // own exception in the reading loop!
while (!cancellationToken.IsCancellationRequested)
{
- Status = MqttClientConnectionStatus.Running;
-
var packet = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
if (packet == null)
{
@@ -184,13 +173,7 @@ namespace MQTTnet.Server
return;
}
- Interlocked.Increment(ref _sentPacketsCount);
- LastPacketReceivedTimestamp = DateTime.UtcNow;
-
- if (!(packet is MqttPingReqPacket || packet is MqttPingRespPacket))
- {
- _lastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp;
- }
+ Statistics.HandleReceivedPacket(packet);
if (packet is MqttPublishPacket publishPacket)
{
@@ -210,7 +193,8 @@ namespace MQTTnet.Server
}
else if (packet is MqttPingReqPacket)
{
- await SendAsync(MqttPingRespPacket.Instance, cancellationToken).ConfigureAwait(false);
+ // See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
+ await SendPacketAsync(MqttPingRespPacket.Instance, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPingRespPacket)
{
@@ -218,10 +202,7 @@ namespace MQTTnet.Server
}
else if (packet is MqttDisconnectPacket)
{
- Session.WillMessage = null;
- disconnectType = MqttClientDisconnectType.Clean;
-
- StopInternal();
+ IsCleanDisconnect = true;
return;
}
else
@@ -246,40 +227,111 @@ namespace MQTTnet.Server
{
_logger.Error(exception, "Client '{0}': Error while receiving client packets.", ClientId);
}
+ }
+ }
+
+ async Task SendPacketsLoop(CancellationToken cancellationToken)
+ {
+ MqttQueuedApplicationMessage queuedApplicationMessage = null;
+ MqttPublishPacket publishPacket = null;
+
+ try
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ queuedApplicationMessage = await Session.ApplicationMessagesQueue.DequeueAsync(cancellationToken).ConfigureAwait(false);
+ if (queuedApplicationMessage == null)
+ {
+ return;
+ }
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage);
+ publishPacket.QualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel;
+
+ // Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9].
+ publishPacket.Retain = queuedApplicationMessage.IsRetainedMessage;
+
+ publishPacket = await InvokeClientMessageQueueInterceptor(publishPacket, queuedApplicationMessage).ConfigureAwait(false);
+ if (publishPacket == null)
+ {
+ // The interceptor has decided that the message is not relevant and will be fully ignored.
+ continue;
+ }
+
+ if (publishPacket.QualityOfServiceLevel > 0)
+ {
+ publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
+ }
+
+ if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
+ {
+ await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
+ }
+ else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
+ {
+ using (var awaiter = _packetDispatcher.AddAwaiter(publishPacket.PacketIdentifier))
+ {
+ await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
+ await awaiter.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
+ }
+ }
+ else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
+ {
+ using (var awaiter1 = _packetDispatcher.AddAwaiter(publishPacket.PacketIdentifier))
+ using (var awaiter2 = _packetDispatcher.AddAwaiter(publishPacket.PacketIdentifier))
+ {
+ await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
+ var pubRecPacket = await awaiter1.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
+
+ var pubRelPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubRelPacket(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
+ await SendPacketAsync(pubRelPacket, cancellationToken).ConfigureAwait(false);
+
+ await awaiter2.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
+ }
+ }
+
+ _logger.Verbose("Client '{0}': Queued application message sent.", ClientId);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch (Exception exception)
+ {
+ if (exception is MqttCommunicationTimedOutException)
+ {
+ _logger.Warning(exception, "Client '{0}': Sending publish packet failed: Timeout.", ClientId);
+ }
+ else if (exception is MqttCommunicationException)
+ {
+ _logger.Warning(exception, "Client '{0}': Sending publish packet failed: Communication exception.", ClientId);
+ }
+ else
+ {
+ _logger.Error(exception, "Client '{0}': Sending publish packet failed.", ClientId);
+ }
+
+ if (publishPacket?.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
+ {
+ if (queuedApplicationMessage != null)
+ {
+ queuedApplicationMessage.IsDuplicate = true;
+ Session.ApplicationMessagesQueue.Enqueue(queuedApplicationMessage);
+ }
+ }
StopInternal();
}
- finally
- {
- if (_disconnectReason == MqttClientDisconnectReason.SessionTakenOver)
- {
- disconnectType = MqttClientDisconnectType.Takeover;
- }
-
- if (Session.WillMessage != null)
- {
- _sessionsManager.DispatchApplicationMessage(Session.WillMessage, this);
- Session.WillMessage = null;
- }
-
- _packetDispatcher.CancelAll();
-
- _logger.Info("Client '{0}': Connection stopped.", ClientId);
-
- try
- {
- await _sessionsManager.CleanUpClient(ClientId, _channelAdapter, disconnectType);
- }
- catch (Exception e)
- {
- _logger.Error(e, "Client '{0}': Error while cleaning up", ClientId);
- }
- }
}
void StopInternal()
{
- _cancellationToken.Cancel();
+ _cancellationToken?.Cancel();
}
async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters)
@@ -294,7 +346,7 @@ namespace MQTTnet.Server
Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{
var pubCompPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubCompPacket(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
- return SendAsync(pubCompPacket, cancellationToken);
+ return SendPacketAsync(pubCompPacket, cancellationToken);
}
async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
@@ -302,7 +354,7 @@ namespace MQTTnet.Server
var subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket, ConnectPacket).ConfigureAwait(false);
var subAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateSubAckPacket(subscribePacket, subscribeResult);
- await SendAsync(subAckPacket, cancellationToken).ConfigureAwait(false);
+ await SendPacketAsync(subAckPacket, cancellationToken).ConfigureAwait(false);
if (subscribeResult.CloseConnection)
{
@@ -318,38 +370,36 @@ namespace MQTTnet.Server
var reasonCodes = await Session.SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
var unsubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateUnsubAckPacket(unsubscribePacket, reasonCodes);
- await SendAsync(unsubAckPacket, cancellationToken).ConfigureAwait(false);
+ await SendPacketAsync(unsubAckPacket, cancellationToken).ConfigureAwait(false);
}
Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
- Interlocked.Increment(ref _sentApplicationMessagesCount);
-
HandleTopicAlias(publishPacket);
- var applicationMessage = _dataConverter.CreateApplicationMessage(publishPacket);
+ var applicationMessage = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket);
_sessionsManager.DispatchApplicationMessage(applicationMessage, this);
switch (publishPacket.QualityOfServiceLevel)
{
case MqttQualityOfServiceLevel.AtMostOnce:
- {
- return PlatformAbstractionLayer.CompletedTask;
- }
+ {
+ return PlatformAbstractionLayer.CompletedTask;
+ }
case MqttQualityOfServiceLevel.AtLeastOnce:
- {
- var pubAckPacket = _dataConverter.CreatePubAckPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
- return SendAsync(pubAckPacket, cancellationToken);
- }
+ {
+ var pubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubAckPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
+ return SendPacketAsync(pubAckPacket, cancellationToken);
+ }
case MqttQualityOfServiceLevel.ExactlyOnce:
- {
- var pubRecPacket = _dataConverter.CreatePubRecPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
- return SendAsync(pubRecPacket, cancellationToken);
- }
+ {
+ var pubRecPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubRecPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
+ return SendPacketAsync(pubRecPacket, cancellationToken);
+ }
default:
- {
- throw new MqttCommunicationException("Received a not supported QoS level.");
- }
+ {
+ throw new MqttCommunicationException("Received a not supported QoS level.");
+ }
}
}
@@ -376,134 +426,64 @@ namespace MQTTnet.Server
}
else
{
-
}
}
}
}
-
- async Task SendPendingPacketsAsync(CancellationToken cancellationToken)
+
+ async Task TrySendDisconnectPacket(MqttClientDisconnectReason reason)
{
- MqttQueuedApplicationMessage queuedApplicationMessage = null;
- MqttPublishPacket publishPacket = null;
-
try
{
- while (!cancellationToken.IsCancellationRequested)
+ var disconnectOptions = new MqttClientDisconnectOptions
{
- queuedApplicationMessage = await Session.ApplicationMessagesQueue.DequeueAsync(cancellationToken).ConfigureAwait(false);
- if (queuedApplicationMessage == null)
- {
- return;
- }
+ ReasonCode = reason,
+ ReasonString = reason.ToString()
+ };
- if (cancellationToken.IsCancellationRequested)
- {
- return;
- }
+ var disconnectPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateDisconnectPacket(disconnectOptions);
- publishPacket = _dataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage);
- publishPacket.QualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel;
-
- // Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9].
- publishPacket.Retain = queuedApplicationMessage.IsRetainedMessage;
-
- if (_serverOptions.ClientMessageQueueInterceptor != null)
- {
- var context = new MqttClientMessageQueueInterceptorContext(
- queuedApplicationMessage.SenderClientId,
- ClientId,
- queuedApplicationMessage.ApplicationMessage,
- queuedApplicationMessage.SubscriptionQualityOfServiceLevel);
-
- if (_serverOptions.ClientMessageQueueInterceptor != null)
- {
- await _serverOptions.ClientMessageQueueInterceptor.InterceptClientMessageQueueEnqueueAsync(context).ConfigureAwait(false);
- }
-
- if (!context.AcceptEnqueue || context.ApplicationMessage == null)
- {
- return;
- }
-
- publishPacket.Topic = context.ApplicationMessage.Topic;
- publishPacket.Payload = context.ApplicationMessage.Payload;
- publishPacket.QualityOfServiceLevel = context.SubscriptionQualityOfServiceLevel;
- }
-
- if (publishPacket.QualityOfServiceLevel > 0)
- {
- publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
- }
-
- if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
- {
- await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
- }
- else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
- {
- var awaiter = _packetDispatcher.AddAwaiter(publishPacket.PacketIdentifier);
- await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
- await awaiter.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
- }
- else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
- {
- using (var awaiter1 = _packetDispatcher.AddAwaiter(publishPacket.PacketIdentifier))
- using (var awaiter2 = _packetDispatcher.AddAwaiter(publishPacket.PacketIdentifier))
- {
- await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
- var pubRecPacket = await awaiter1.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
-
- var pubRelPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubRelPacket(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
- await SendAsync(pubRelPacket, cancellationToken).ConfigureAwait(false);
-
- await awaiter2.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
- }
- }
-
- _logger.Verbose("Client '{0}': Queued application message sent.", ClientId);
+ using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
+ {
+ await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
}
}
catch (Exception exception)
{
- if (exception is OperationCanceledException)
- {
- }
- else if (exception is MqttCommunicationTimedOutException)
- {
- _logger.Warning(exception, "Client '{0}': Sending publish packet failed: Timeout.", ClientId);
- }
- else if (exception is MqttCommunicationException)
- {
- _logger.Warning(exception, "Client '{0}': Sending publish packet failed: Communication exception.", ClientId);
- }
- else
- {
- _logger.Error(exception, "Client '{0}': Sending publish packet failed.", ClientId);
- }
-
- if (publishPacket?.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
- {
- queuedApplicationMessage.IsDuplicate = true;
-
- Session.ApplicationMessagesQueue.Enqueue(queuedApplicationMessage);
- }
-
- StopInternal();
+ _logger.Warning(exception, "Client '{{0}}': Error while sending DISCONNECT packet (Reason = {1}).", ClientId, reason);
}
}
- Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken)
+ async Task InvokeClientMessageQueueInterceptor(MqttPublishPacket publishPacket, MqttQueuedApplicationMessage queuedApplicationMessage)
{
- return _channelAdapter.SendPacketAsync(packet, cancellationToken).ContinueWith(task =>
+ if (_serverOptions.ClientMessageQueueInterceptor == null)
{
- Interlocked.Increment(ref _receivedPacketsCount);
+ return publishPacket;
+ }
- if (packet is MqttPublishPacket)
- {
- Interlocked.Increment(ref _receivedApplicationMessagesCount);
- }
- }, cancellationToken);
+ var context = new MqttClientMessageQueueInterceptorContext
+ {
+ SenderClientId = queuedApplicationMessage.SenderClientId,
+ ReceiverClientId = ClientId,
+ ApplicationMessage = queuedApplicationMessage.ApplicationMessage,
+ SubscriptionQualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel
+ };
+
+ if (_serverOptions.ClientMessageQueueInterceptor != null)
+ {
+ await _serverOptions.ClientMessageQueueInterceptor.InterceptClientMessageQueueEnqueueAsync(context).ConfigureAwait(false);
+ }
+
+ if (!context.AcceptEnqueue || context.ApplicationMessage == null)
+ {
+ return null;
+ }
+
+ publishPacket.Topic = context.ApplicationMessage.Topic;
+ publishPacket.Payload = context.ApplicationMessage.Payload;
+ publishPacket.QualityOfServiceLevel = context.SubscriptionQualityOfServiceLevel;
+
+ return publishPacket;
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Server/Internal/MqttClientConnectionStatistics.cs b/Source/MQTTnet/Server/Internal/MqttClientConnectionStatistics.cs
new file mode 100644
index 00000000..14923f1b
--- /dev/null
+++ b/Source/MQTTnet/Server/Internal/MqttClientConnectionStatistics.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Threading;
+using MQTTnet.Packets;
+using MQTTnet.Server.Status;
+
+namespace MQTTnet.Server.Internal
+{
+ public sealed class MqttClientConnectionStatistics
+ {
+ readonly DateTime _connectedTimestamp;
+
+ DateTime _lastNonKeepAlivePacketReceivedTimestamp;
+ DateTime _lastPacketReceivedTimestamp;
+ DateTime _lastPacketSentTimestamp;
+
+ // Start with 1 because the CONNACK packet is not counted here.
+ long _receivedPacketsCount = 1;
+
+ // Start with 1 because the CONNECT packet is not counted here.
+ long _sentPacketsCount = 1;
+
+ long _receivedApplicationMessagesCount;
+ long _sentApplicationMessagesCount;
+
+ public MqttClientConnectionStatistics()
+ {
+ _connectedTimestamp = DateTime.UtcNow;
+
+ _lastPacketReceivedTimestamp = _connectedTimestamp;
+ _lastPacketSentTimestamp = _connectedTimestamp;
+
+ _lastNonKeepAlivePacketReceivedTimestamp = _connectedTimestamp;
+ }
+
+ public DateTime LastPacketReceivedTimestamp => _lastPacketReceivedTimestamp;
+
+ public void HandleReceivedPacket(MqttBasePacket packet)
+ {
+ if (packet == null) throw new ArgumentNullException(nameof(packet));
+
+ // This class is tracking all values from Clients perspective!
+ _lastPacketSentTimestamp = DateTime.UtcNow;
+
+ Interlocked.Increment(ref _sentPacketsCount);
+
+ if (packet is MqttPublishPacket)
+ {
+ Interlocked.Increment(ref _sentApplicationMessagesCount);
+ }
+
+ if (!(packet is MqttPingReqPacket || packet is MqttPingRespPacket))
+ {
+ _lastNonKeepAlivePacketReceivedTimestamp = _lastPacketReceivedTimestamp;
+ }
+ }
+
+ public void HandleSentPacket(MqttBasePacket packet)
+ {
+ if (packet == null) throw new ArgumentNullException(nameof(packet));
+
+ // This class is tracking all values from Clients perspective!
+ _lastPacketReceivedTimestamp = DateTime.UtcNow;
+
+ Interlocked.Increment(ref _receivedPacketsCount);
+
+ if (packet is MqttPublishPacket)
+ {
+ Interlocked.Increment(ref _receivedApplicationMessagesCount);
+ }
+ }
+
+ public void FillClientStatus(MqttClientStatus clientStatus)
+ {
+ if (clientStatus == null) throw new ArgumentNullException(nameof(clientStatus));
+
+ clientStatus.ConnectedTimestamp = _connectedTimestamp;
+
+ clientStatus.ReceivedPacketsCount = Interlocked.Read(ref _receivedPacketsCount);
+ clientStatus.SentPacketsCount = Interlocked.Read(ref _sentPacketsCount);
+
+ clientStatus.ReceivedApplicationMessagesCount = Interlocked.Read(ref _receivedApplicationMessagesCount);
+ clientStatus.SentApplicationMessagesCount = Interlocked.Read(ref _sentApplicationMessagesCount);
+
+ clientStatus.LastPacketReceivedTimestamp = _lastPacketReceivedTimestamp;
+ clientStatus.LastPacketSentTimestamp = _lastPacketSentTimestamp;
+
+ clientStatus.LastNonKeepAlivePacketReceivedTimestamp = _lastNonKeepAlivePacketReceivedTimestamp;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/Internal/MqttClientSession.cs
similarity index 96%
rename from Source/MQTTnet/Server/MqttClientSession.cs
rename to Source/MQTTnet/Server/Internal/MqttClientSession.cs
index 620c4c96..524f70b1 100644
--- a/Source/MQTTnet/Server/MqttClientSession.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClientSession.cs
@@ -1,10 +1,10 @@
-using MQTTnet.Diagnostics;
-using MQTTnet.Server.Status;
-using System;
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using MQTTnet.Diagnostics;
+using MQTTnet.Server.Status;
-namespace MQTTnet.Server
+namespace MQTTnet.Server.Internal
{
public sealed class MqttClientSession
{
@@ -81,7 +81,7 @@ namespace MQTTnet.Server
return SubscriptionsManager.UnsubscribeAsync(topicFilters);
}
- public void FillStatus(MqttSessionStatus status)
+ public void FillSessionStatus(MqttSessionStatus status)
{
status.ClientId = ClientId;
status.CreatedTimestamp = _createdTimestamp;
diff --git a/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs b/Source/MQTTnet/Server/Internal/MqttClientSessionApplicationMessagesQueue.cs
similarity index 97%
rename from Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs
rename to Source/MQTTnet/Server/Internal/MqttClientSessionApplicationMessagesQueue.cs
index 6ddba92d..e24bde03 100644
--- a/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClientSessionApplicationMessagesQueue.cs
@@ -1,10 +1,10 @@
-using MQTTnet.Internal;
-using MQTTnet.Protocol;
-using System;
+using System;
using System.Threading;
using System.Threading.Tasks;
+using MQTTnet.Internal;
+using MQTTnet.Protocol;
-namespace MQTTnet.Server
+namespace MQTTnet.Server.Internal
{
public sealed class MqttClientSessionApplicationMessagesQueue : IDisposable
{
diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
similarity index 76%
rename from Source/MQTTnet/Server/MqttClientSessionsManager.cs
rename to Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
index ebc43985..80945bea 100644
--- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
@@ -1,4 +1,11 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using MQTTnet.Adapter;
+using MQTTnet.Client.Disconnecting;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
@@ -6,15 +13,8 @@ using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server.Status;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MQTTnet.Client.Disconnecting;
-namespace MQTTnet.Server
+namespace MQTTnet.Server.Internal
{
public sealed class MqttClientSessionsManager : IDisposable
{
@@ -53,53 +53,66 @@ namespace MQTTnet.Server
Task.Run(() => TryProcessQueuedApplicationMessagesAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
}
- public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
+ async Task ReceiveConnectPacket(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
{
try
{
- MqttConnectPacket connectPacket;
- try
+ using (var timeoutToken = new CancellationTokenSource(_options.DefaultCommunicationTimeout))
+ using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken.Token, cancellationToken))
{
- using (var timeoutToken = new CancellationTokenSource(_options.DefaultCommunicationTimeout))
- {
- var firstPacket = await channelAdapter.ReceivePacketAsync(timeoutToken.Token).ConfigureAwait(false);
- connectPacket = firstPacket as MqttConnectPacket;
- if (connectPacket == null)
- {
- _logger.Warning(null,
- "Client '{0}': First received packet was no 'CONNECT' packet [MQTT-3.1.0-1].",
- channelAdapter.Endpoint);
+ var firstPacket = await channelAdapter.ReceivePacketAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
- return;
- }
+ if (firstPacket is MqttConnectPacket connectPacket)
+ {
+ return connectPacket;
}
}
- catch (OperationCanceledException)
+ }
+ catch (OperationCanceledException)
+ {
+ _logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint);
+ }
+ catch (MqttCommunicationTimedOutException)
+ {
+ _logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint);
+ }
+
+ _logger.Warning("Client '{0}': First received packet was no 'CONNECT' packet [MQTT-3.1.0-1].", channelAdapter.Endpoint);
+ return null;
+ }
+
+ public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
+ {
+ MqttClientConnection clientConnection = null;
+
+ try
+ {
+ var connectPacket = await ReceiveConnectPacket(channelAdapter, cancellationToken).ConfigureAwait(false);
+ if (connectPacket == null)
{
- _logger.Warning(null, "Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint);
- return;
- }
- catch (MqttCommunicationTimedOutException)
- {
- _logger.Warning(null, "Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint);
+ // Nothing was received in time etc.
return;
}
- var connectionValidatorContext = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false);
-
+ MqttConnAckPacket connAckPacket;
+
+ var connectionValidatorContext = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success)
{
- // Send failure response here without preparing a session. The result for a successful connect
- // will be sent from the session itself.
- var connAckPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext);
+ // Send failure response here without preparing a session!
+ connAckPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext);
await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false);
-
return;
}
- var connection = await CreateClientConnectionAsync(connectPacket, connectionValidatorContext, channelAdapter).ConfigureAwait(false);
- await _eventDispatcher.SafeNotifyClientConnectedAsync(connectPacket.ClientId).ConfigureAwait(false);
- await connection.RunAsync().ConfigureAwait(false);
+ clientConnection = await CreateClientConnection(connectPacket, channelAdapter, connectionValidatorContext.SessionItems).ConfigureAwait(false);
+
+ connAckPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext);
+ await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false);
+
+ await _eventDispatcher.SafeNotifyClientConnectedAsync(connectPacket, channelAdapter).ConfigureAwait(false);
+
+ await clientConnection.RunAsync().ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -108,6 +121,41 @@ namespace MQTTnet.Server
{
_logger.Error(exception, exception.Message);
}
+ finally
+ {
+ if (clientConnection != null)
+ {
+ if (clientConnection.ClientId != null)
+ {
+ // in case it is a takeover _connections already contains the new connection
+ if (!clientConnection.IsTakenOver)
+ {
+ lock (_connections)
+ {
+ _connections.Remove(clientConnection.ClientId);
+ }
+
+ if (!_options.EnablePersistentSessions)
+ {
+ await DeleteSessionAsync(clientConnection.ClientId).ConfigureAwait(false);
+ }
+ }
+ }
+
+ var endpoint = clientConnection.Endpoint;
+
+ if (clientConnection.ClientId != null && !clientConnection.IsTakenOver)
+ {
+ // The event is fired at a separate place in case of a handover!
+ await _eventDispatcher.SafeNotifyClientDisconnectedAsync(
+ clientConnection.ClientId,
+ clientConnection.IsCleanDisconnect ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean,
+ endpoint).ConfigureAwait(false);
+ }
+ }
+
+ await channelAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
+ }
}
public async Task CloseAllConnectionsAsync()
@@ -132,7 +180,7 @@ namespace MQTTnet.Server
return _connections.Values.ToList();
}
}
-
+
public Task> GetClientStatusAsync()
{
var result = new List();
@@ -142,10 +190,10 @@ namespace MQTTnet.Server
foreach (var connection in _connections.Values)
{
var clientStatus = new MqttClientStatus(connection);
- connection.FillStatus(clientStatus);
+ connection.FillClientStatus(clientStatus);
var sessionStatus = new MqttSessionStatus(connection.Session, this);
- connection.Session.FillStatus(sessionStatus);
+ connection.Session.FillSessionStatus(sessionStatus);
clientStatus.Session = sessionStatus;
result.Add(clientStatus);
@@ -164,7 +212,7 @@ namespace MQTTnet.Server
foreach (var session in _sessions.Values)
{
var sessionStatus = new MqttSessionStatus(session, this);
- session.FillStatus(sessionStatus);
+ session.FillSessionStatus(sessionStatus);
result.Add(sessionStatus);
}
@@ -203,7 +251,7 @@ namespace MQTTnet.Server
{
_connections.TryGetValue(clientId, out connection);
}
-
+
lock (_sessions)
{
_sessions.Remove(clientId);
@@ -213,39 +261,10 @@ namespace MQTTnet.Server
{
await connection.StopAsync(MqttClientDisconnectReason.NormalDisconnection).ConfigureAwait(false);
}
-
+
_logger.Verbose("Session for client '{0}' deleted.", clientId);
}
- public async Task CleanUpClient(string clientId, IMqttChannelAdapter channelAdapter, MqttClientDisconnectType disconnectType)
- {
- if (clientId != null)
- {
- // in case it is a takeover _connections already contains the new connection
- if (disconnectType != MqttClientDisconnectType.Takeover)
- {
- lock (_connections)
- {
- _connections.Remove(clientId);
- }
-
- if (!_options.EnablePersistentSessions)
- {
- await DeleteSessionAsync(clientId).ConfigureAwait(false);
- }
- }
- }
-
- var endpoint = channelAdapter.Endpoint;
-
- await SafeCleanupChannelAsync(channelAdapter).ConfigureAwait(false);
-
- if (clientId != null)
- {
- await _eventDispatcher.SafeNotifyClientDisconnectedAsync(clientId, disconnectType, endpoint).ConfigureAwait(false);
- }
- }
-
public void Dispose()
{
_messageQueue?.Dispose();
@@ -278,7 +297,7 @@ namespace MQTTnet.Server
MqttPendingApplicationMessage queuedApplicationMessage;
try
{
- queuedApplicationMessage = _messageQueue.Take(cancellationToken);
+ queuedApplicationMessage = _messageQueue.Take(cancellationToken);
}
catch (ArgumentNullException)
{
@@ -315,7 +334,7 @@ namespace MQTTnet.Server
applicationMessage = interceptorContext.ApplicationMessage;
}
}
-
+
await _eventDispatcher.SafeNotifyApplicationMessageReceivedAsync(senderClientId, applicationMessage).ConfigureAwait(false);
if (applicationMessage.Retain)
@@ -360,9 +379,12 @@ namespace MQTTnet.Server
}
}
- async Task ValidateConnectionAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
+ async Task ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
{
- var context = new MqttConnectionValidatorContext(connectPacket, channelAdapter, new ConcurrentDictionary