Improve internal server connection management. (#1232)

This commit is contained in:
Christian
2021-09-08 18:01:56 +02:00
committed by GitHub
parent 9d9077f17c
commit cd8940f9b7
60 changed files with 980 additions and 589 deletions
+4
View File
@@ -13,6 +13,10 @@
<description>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.</description>
<releaseNotes>
* [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
+3 -3
View File
@@ -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;
@@ -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)
{
@@ -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<MqttApplicationMessageReceivedEventArgs, Task> _handler;
readonly Func<MqttApplicationMessageReceivedEventArgs, Task> _handler;
public MqttApplicationMessageReceivedHandlerDelegate(Action<MqttApplicationMessageReceivedEventArgs> handler)
{
@@ -14,7 +15,7 @@ namespace MQTTnet.Client.Receiving
_handler = context =>
{
handler(context);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
@@ -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);
+1
View File
@@ -8,6 +8,7 @@ namespace MQTTnet
{
}
// TODO: Consider using struct instead.
public class MqttTopicFilter
{
/// <summary>
@@ -3,7 +3,9 @@
public enum MqttRetainHandling
{
SendAtSubscribe = 0,
SendAtSubscribeIfNewSubscriptionOnly = 1,
DoNotSendOnSubscribe = 2
}
}
@@ -1,6 +1,6 @@
using MQTTnet.Protocol;
namespace MQTTnet.Server
namespace MQTTnet.Server.Internal
{
public struct CheckSubscriptionsResult
{
@@ -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<ushort, string> _topicAlias = new Dictionary<ushort, string>();
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<MqttPubAckPacket>(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<MqttPubRecPacket>(publishPacket.PacketIdentifier))
using (var awaiter2 = _packetDispatcher.AddAwaiter<MqttPubCompPacket>(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<MqttTopicFilter> 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<MqttPubAckPacket>(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<MqttPubRecPacket>(publishPacket.PacketIdentifier))
using (var awaiter2 = _packetDispatcher.AddAwaiter<MqttPubCompPacket>(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<MqttPublishPacket> 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;
}
}
}
}
@@ -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;
}
}
}
@@ -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;
@@ -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
{
@@ -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<MqttConnectPacket> 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<IList<IMqttClientStatus>> GetClientStatusAsync()
{
var result = new List<IMqttClientStatus>();
@@ -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<MqttConnectionValidatorContext> ValidateConnectionAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
async Task<MqttConnectionValidatorContext> ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
{
var context = new MqttConnectionValidatorContext(connectPacket, channelAdapter, new ConcurrentDictionary<object, object>());
var context = new MqttConnectionValidatorContext(connectPacket, channelAdapter)
{
SessionItems = new ConcurrentDictionary<object, object>()
};
var connectionValidator = _options.ConnectionValidator;
@@ -388,9 +410,8 @@ namespace MQTTnet.Server
return context;
}
async Task<MqttClientConnection> CreateClientConnectionAsync(MqttConnectPacket connectPacket, MqttConnectionValidatorContext connectionValidatorContext, IMqttChannelAdapter channelAdapter)
async Task<MqttClientConnection> CreateClientConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, IDictionary<object, object> sessionItems)
{
MqttClientConnection existingConnection;
MqttClientConnection connection;
using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false))
@@ -401,14 +422,14 @@ namespace MQTTnet.Server
if (!_sessions.TryGetValue(connectPacket.ClientId, out session))
{
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
session = CreateSession(connectPacket.ClientId, connectionValidatorContext);
session = CreateSession(connectPacket.ClientId, sessionItems);
}
else
{
if (connectPacket.CleanSession)
{
_logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId);
session = CreateSession(connectPacket.ClientId, connectionValidatorContext);
session = CreateSession(connectPacket.ClientId, sessionItems);
}
else
{
@@ -419,16 +440,21 @@ namespace MQTTnet.Server
_sessions[connectPacket.ClientId] = session;
}
MqttClientConnection existingConnection;
lock (_connections)
{
_connections.TryGetValue(connectPacket.ClientId, out existingConnection);
connection = CreateConnection(connectPacket, channelAdapter, session, connectionValidatorContext);
connection = CreateConnection(connectPacket, channelAdapter, session);
_connections[connectPacket.ClientId] = connection;
}
if (existingConnection != null)
{
await _eventDispatcher.SafeNotifyClientDisconnectedAsync(existingConnection.ClientId, MqttClientDisconnectType.Takeover, existingConnection.Endpoint);
existingConnection.IsTakenOver = true;
await existingConnection.StopAsync(MqttClientDisconnectReason.SessionTakenOver).ConfigureAwait(false);
}
}
@@ -436,7 +462,8 @@ namespace MQTTnet.Server
return connection;
}
async Task<MqttApplicationMessageInterceptorContext> InterceptApplicationMessageAsync(IMqttServerApplicationMessageInterceptor interceptor, MqttClientConnection clientConnection, MqttApplicationMessage applicationMessage)
async Task<MqttApplicationMessageInterceptorContext> InterceptApplicationMessageAsync(IMqttServerApplicationMessageInterceptor interceptor, MqttClientConnection clientConnection,
MqttApplicationMessage applicationMessage)
{
string senderClientId;
IDictionary<object, object> sessionItems;
@@ -453,29 +480,20 @@ namespace MQTTnet.Server
sessionItems = clientConnection.Session.Items;
}
var interceptorContext = new MqttApplicationMessageInterceptorContext(senderClientId, sessionItems, _logger)
var interceptorContext = new MqttApplicationMessageInterceptorContext
{
ClientId = senderClientId,
SessionItems = sessionItems,
Logger = _logger,
AcceptPublish = true,
ApplicationMessage = applicationMessage,
CloseConnection = false
CloseConnection = false
};
await interceptor.InterceptApplicationMessagePublishAsync(interceptorContext).ConfigureAwait(false);
return interceptorContext;
}
async Task SafeCleanupChannelAsync(IMqttChannelAdapter channelAdapter)
{
try
{
await channelAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, "Error while disconnecting client channel.");
}
}
MqttClientSession GetSession(string clientId)
{
lock (_sessions)
@@ -489,24 +507,23 @@ namespace MQTTnet.Server
}
}
MqttClientConnection CreateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttClientSession session, MqttConnectionValidatorContext connectionValidatorContext)
MqttClientConnection CreateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttClientSession session)
{
return new MqttClientConnection(
connectPacket,
channelAdapter,
session,
connectionValidatorContext,
_options,
this,
_retainedMessagesManager,
_rootLogger);
}
MqttClientSession CreateSession(string clientId, MqttConnectionValidatorContext connectionValidatorContext)
MqttClientSession CreateSession(string clientId, IDictionary<object, object> sessionItems)
{
return new MqttClientSession(
clientId,
connectionValidatorContext.SessionItems,
sessionItems,
_eventDispatcher,
_options,
_retainedMessagesManager,
@@ -1,12 +1,12 @@
using MQTTnet.Packets;
using MQTTnet.Protocol;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
namespace MQTTnet.Server.Internal
{
public sealed class MqttClientSubscriptionsManager
{
@@ -37,9 +37,11 @@ namespace MQTTnet.Server
{
var interceptorContext = await InterceptSubscribeAsync(originalTopicFilter).ConfigureAwait(false);
var finalTopicFilter = interceptorContext.TopicFilter;
var finalTopicFilter = interceptorContext?.TopicFilter ?? originalTopicFilter;
var acceptSubscription = interceptorContext?.AcceptSubscription ?? true;
var closeConnection = interceptorContext?.CloseConnection ?? false;
if (finalTopicFilter == null || string.IsNullOrEmpty(finalTopicFilter.Topic) || !interceptorContext.AcceptSubscription)
if (finalTopicFilter == null || string.IsNullOrEmpty(finalTopicFilter.Topic) || !acceptSubscription)
{
result.ReturnCodes.Add(MqttSubscribeReturnCode.Failure);
result.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError);
@@ -50,12 +52,12 @@ namespace MQTTnet.Server
result.ReasonCodes.Add(ConvertToSubscribeReasonCode(finalTopicFilter.QualityOfServiceLevel));
}
if (interceptorContext.CloseConnection)
if (closeConnection)
{
result.CloseConnection = true;
}
if (!interceptorContext.AcceptSubscription || string.IsNullOrEmpty(finalTopicFilter?.Topic))
if (!acceptSubscription || string.IsNullOrEmpty(finalTopicFilter?.Topic))
{
continue;
}
@@ -83,12 +85,7 @@ namespace MQTTnet.Server
foreach (var topicFilter in topicFilters)
{
var interceptorContext = await InterceptSubscribeAsync(topicFilter).ConfigureAwait(false);
if (!interceptorContext.AcceptSubscription)
{
continue;
}
if (!interceptorContext.AcceptSubscription)
if (interceptorContext != null && !interceptorContext.AcceptSubscription)
{
continue;
}
@@ -116,7 +113,7 @@ namespace MQTTnet.Server
foreach (var topicFilter in unsubscribePacket.TopicFilters)
{
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
if (!interceptorContext.AcceptUnsubscription)
if (interceptorContext != null && !interceptorContext.AcceptUnsubscription)
{
reasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError);
continue;
@@ -150,7 +147,7 @@ namespace MQTTnet.Server
foreach (var topicFilter in topicFilters)
{
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
if (!interceptorContext.AcceptUnsubscription)
if (interceptorContext != null && !interceptorContext.AcceptUnsubscription)
{
continue;
}
@@ -187,7 +184,7 @@ namespace MQTTnet.Server
{
continue;
}
qosLevels.Add(subscription.Value.QualityOfServiceLevel);
}
@@ -211,23 +208,41 @@ namespace MQTTnet.Server
async Task<MqttSubscriptionInterceptorContext> InterceptSubscribeAsync(MqttTopicFilter topicFilter)
{
var context = new MqttSubscriptionInterceptorContext(_clientSession.ClientId, topicFilter, _clientSession.Items);
if (_options.SubscriptionInterceptor != null)
var interceptor = _options.SubscriptionInterceptor;
if (interceptor == null)
{
await _options.SubscriptionInterceptor.InterceptSubscriptionAsync(context).ConfigureAwait(false);
return null;
}
var context = new MqttSubscriptionInterceptorContext
{
ClientId = _clientSession.ClientId,
TopicFilter = topicFilter,
SessionItems = _clientSession.Items
};
await interceptor.InterceptSubscriptionAsync(context).ConfigureAwait(false);
return context;
}
async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribeAsync(string topicFilter)
{
var context = new MqttUnsubscriptionInterceptorContext(_clientSession.ClientId, topicFilter, _clientSession.Items);
if (_options.UnsubscriptionInterceptor != null)
var interceptor = _options.UnsubscriptionInterceptor;
if (interceptor == null)
{
await _options.UnsubscriptionInterceptor.InterceptUnsubscriptionAsync(context).ConfigureAwait(false);
return null;
}
var context = new MqttUnsubscriptionInterceptorContext
{
ClientId = _clientSession.ClientId,
Topic = topicFilter,
SessionItems = _clientSession.Items
};
await interceptor.InterceptUnsubscriptionAsync(context).ConfigureAwait(false);
return context;
}
@@ -254,4 +269,4 @@ namespace MQTTnet.Server
};
}
}
}
}
@@ -1,13 +1,13 @@
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Server
namespace MQTTnet.Server.Internal
{
public sealed class MqttRetainedMessagesManager : IMqttRetainedMessagesManager
{
@@ -1,9 +1,11 @@
using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using System;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using MQTTnet.Packets;
namespace MQTTnet.Server
namespace MQTTnet.Server.Internal
{
public sealed class MqttServerEventDispatcher
{
@@ -26,7 +28,7 @@ namespace MQTTnet.Server
public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; }
public async Task SafeNotifyClientConnectedAsync(string clientId)
public async Task SafeNotifyClientConnectedAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
{
try
{
@@ -36,7 +38,13 @@ namespace MQTTnet.Server
return;
}
await handler.HandleClientConnectedAsync(new MqttServerClientConnectedEventArgs(clientId)).ConfigureAwait(false);
await handler.HandleClientConnectedAsync(new MqttServerClientConnectedEventArgs
{
ClientId = connectPacket.ClientId,
UserName = connectPacket.Username,
ProtocolVersion = channelAdapter.PacketFormatterAdapter.ProtocolVersion,
Endpoint = channelAdapter.Endpoint
}).ConfigureAwait(false);
}
catch (Exception exception)
{
@@ -54,7 +62,12 @@ namespace MQTTnet.Server
return;
}
await handler.HandleClientDisconnectedAsync(new MqttServerClientDisconnectedEventArgs(clientId, disconnectType, endpoint)).ConfigureAwait(false);
await handler.HandleClientDisconnectedAsync(new MqttServerClientDisconnectedEventArgs
{
ClientId = clientId,
DisconnectType = disconnectType,
Endpoint = endpoint
}).ConfigureAwait(false);
}
catch (Exception exception)
{
@@ -72,7 +85,11 @@ namespace MQTTnet.Server
return;
}
await handler.HandleClientSubscribedTopicAsync(new MqttServerClientSubscribedTopicEventArgs(clientId, topicFilter)).ConfigureAwait(false);
await handler.HandleClientSubscribedTopicAsync(new MqttServerClientSubscribedTopicEventArgs
{
ClientId = clientId,
TopicFilter = topicFilter
}).ConfigureAwait(false);
}
catch (Exception exception)
{
@@ -90,7 +107,11 @@ namespace MQTTnet.Server
return;
}
await handler.HandleClientUnsubscribedTopicAsync(new MqttServerClientUnsubscribedTopicEventArgs(clientId, topicFilter)).ConfigureAwait(false);
await handler.HandleClientUnsubscribedTopicAsync(new MqttServerClientUnsubscribedTopicEventArgs
{
ClientId = clientId,
TopicFilter = topicFilter
}).ConfigureAwait(false);
}
catch (Exception exception)
{
@@ -1,12 +1,12 @@
using MQTTnet.Diagnostics;
using MQTTnet.Internal;
using System;
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Server
namespace MQTTnet.Server.Internal
{
public sealed class MqttServerKeepAliveMonitor
{
@@ -70,7 +70,7 @@ namespace MQTTnet.Server
{
try
{
if (connection.Status != MqttClientConnectionStatus.Running)
if (!connection.IsRunning)
{
// The connection is already dead or just created so there is no need to check it.
return;
@@ -94,14 +94,14 @@ namespace MQTTnet.Server
// If the client sends 1 sec. the server will allow up to 1.5 seconds.
var maxDurationWithoutPacket = connection.ConnectPacket.KeepAlivePeriod * 1.5D;
var secondsWithoutPackage = (now - connection.LastPacketReceivedTimestamp).TotalSeconds;
var secondsWithoutPackage = (now - connection.Statistics.LastPacketReceivedTimestamp).TotalSeconds;
if (secondsWithoutPackage < maxDurationWithoutPacket)
{
// A packet was received before the timeout is affected.
return;
}
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", connection.ClientId);
_logger.Warning("Client '{0}': Did not receive any packet or keep alive signal.", connection.ClientId);
// Execute the disconnection in background so that the keep alive monitor can continue
// with checking other connections.
@@ -1,6 +1,6 @@
using System;
namespace MQTTnet.Server
namespace MQTTnet.Server.Internal
{
public static class MqttTopicFilterComparer
{
@@ -1,35 +1,27 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using MQTTnet.Diagnostics;
namespace MQTTnet.Server
{
public sealed class MqttApplicationMessageInterceptorContext
{
public MqttApplicationMessageInterceptorContext(string clientId, IDictionary<object, object> sessionItems, IMqttNetScopedLogger logger)
{
ClientId = clientId;
SessionItems = sessionItems;
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Gets the currently used logger.
/// </summary>
public IMqttNetScopedLogger Logger { get; }
public IMqttNetScopedLogger Logger { get; internal set; }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
public string ClientId { get; internal set; }
public MqttApplicationMessage ApplicationMessage { get; set; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary<object, object> SessionItems { get; }
public IDictionary<object, object> SessionItems { get; internal set; }
public bool AcceptPublish { get; set; } = true;
@@ -1,11 +0,0 @@
namespace MQTTnet.Server
{
public enum MqttClientConnectionStatus
{
Initializing,
Running,
Finalizing
}
}
@@ -2,19 +2,11 @@
namespace MQTTnet.Server
{
public class MqttClientMessageQueueInterceptorContext
public sealed class MqttClientMessageQueueInterceptorContext
{
public MqttClientMessageQueueInterceptorContext(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, MqttQualityOfServiceLevel subscriptionQualityOfServiceLevel)
{
SenderClientId = senderClientId;
ReceiverClientId = receiverClientId;
ApplicationMessage = applicationMessage;
SubscriptionQualityOfServiceLevel = subscriptionQualityOfServiceLevel;
}
public string SenderClientId { get; internal set; }
public string SenderClientId { get; }
public string ReceiverClientId { get; }
public string ReceiverClientId { get; internal set; }
public MqttApplicationMessage ApplicationMessage { get; set; }
@@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public class MqttClientMessageQueueInterceptorDelegate : IMqttServerClientMessageQueueInterceptor
public sealed class MqttClientMessageQueueInterceptorDelegate : IMqttServerClientMessageQueueInterceptor
{
private readonly Func<MqttClientMessageQueueInterceptorContext, Task> _callback;
readonly Func<MqttClientMessageQueueInterceptorContext, Task> _callback;
public MqttClientMessageQueueInterceptorDelegate(Action<MqttClientMessageQueueInterceptorContext> callback)
{
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_callback = context =>
{
callback(context);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
@@ -4,21 +4,21 @@ using System.Security.Cryptography.X509Certificates;
using System.Text;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public class MqttConnectionValidatorContext
public sealed class MqttConnectionValidatorContext
{
private readonly MqttConnectPacket _connectPacket;
private readonly IMqttChannelAdapter _clientAdapter;
readonly MqttConnectPacket _connectPacket;
readonly IMqttChannelAdapter _clientAdapter;
public MqttConnectionValidatorContext(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter, IDictionary<object, object> sessionItems)
public MqttConnectionValidatorContext(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
{
_connectPacket = connectPacket;
_clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
SessionItems = sessionItems;
}
/// <summary>
@@ -39,7 +39,7 @@ namespace MQTTnet.Server
public byte[] RawPassword => _connectPacket?.Password;
public string Password => Encoding.UTF8.GetString(RawPassword ?? new byte[0]);
public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
/// <summary>
/// Gets or sets the will delay interval.
@@ -128,7 +128,7 @@ namespace MQTTnet.Server
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary<object, object> SessionItems { get; }
public IDictionary<object, object> SessionItems { get; internal set; }
/// <summary>
/// This is used for MQTTv3 only.
@@ -1,4 +1,6 @@
namespace MQTTnet.Server
using MQTTnet.Server.Internal;
namespace MQTTnet.Server
{
public sealed class MqttPendingApplicationMessage
{
@@ -3,7 +3,7 @@ using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public class MqttQueuedApplicationMessage
public sealed class MqttQueuedApplicationMessage
{
public MqttApplicationMessage ApplicationMessage { get; set; }
+1
View File
@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Server.Internal;
namespace MQTTnet.Server
{
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_callback = context =>
{
callback(context);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
@@ -1,18 +1,29 @@
using System;
using MQTTnet.Formatter;
namespace MQTTnet.Server
{
public class MqttServerClientConnectedEventArgs : EventArgs
public sealed class MqttServerClientConnectedEventArgs : EventArgs
{
public MqttServerClientConnectedEventArgs(string clientId)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
}
/// <summary>
/// Gets the client identifier.
/// Gets the client identifier of the connected client.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
public string ClientId { get; internal set; }
/// <summary>
/// Gets the user name of the connected client.
/// </summary>
public string UserName { get; internal set; }
/// <summary>
/// Gets the protocol version which is used by the connected client.
/// </summary>
public MqttProtocolVersion ProtocolVersion { get; internal set; }
/// <summary>
/// Gets the endpoint of the connected client.
/// </summary>
public string Endpoint { get; internal set; }
}
}
@@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public class MqttServerClientConnectedHandlerDelegate : IMqttServerClientConnectedHandler
public sealed class MqttServerClientConnectedHandlerDelegate : IMqttServerClientConnectedHandler
{
private readonly Func<MqttServerClientConnectedEventArgs, Task> _handler;
readonly Func<MqttServerClientConnectedEventArgs, Task> _handler;
public MqttServerClientConnectedHandlerDelegate(Action<MqttServerClientConnectedEventArgs> handler)
{
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_handler = eventArgs =>
{
handler(eventArgs);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
@@ -2,23 +2,16 @@ using System;
namespace MQTTnet.Server
{
public class MqttServerClientDisconnectedEventArgs : EventArgs
public sealed class MqttServerClientDisconnectedEventArgs : EventArgs
{
public MqttServerClientDisconnectedEventArgs(string clientId, MqttClientDisconnectType disconnectType, string endpoint)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
DisconnectType = disconnectType;
Endpoint = endpoint;
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
public string ClientId { get; internal set; }
public MqttClientDisconnectType DisconnectType { get; }
public MqttClientDisconnectType DisconnectType { get; internal set; }
public string Endpoint { get; }
public string Endpoint { get; internal set; }
}
}
@@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public class MqttServerClientDisconnectedHandlerDelegate : IMqttServerClientDisconnectedHandler
public sealed class MqttServerClientDisconnectedHandlerDelegate : IMqttServerClientDisconnectedHandler
{
private readonly Func<MqttServerClientDisconnectedEventArgs, Task> _handler;
readonly Func<MqttServerClientDisconnectedEventArgs, Task> _handler;
public MqttServerClientDisconnectedHandlerDelegate(Action<MqttServerClientDisconnectedEventArgs> handler)
{
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_handler = eventArgs =>
{
handler(eventArgs);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
@@ -2,24 +2,18 @@
namespace MQTTnet.Server
{
public class MqttServerClientSubscribedTopicEventArgs : EventArgs
public sealed class MqttServerClientSubscribedTopicEventArgs : EventArgs
{
public MqttServerClientSubscribedTopicEventArgs(string clientId, MqttTopicFilter topicFilter)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
public string ClientId { get; internal set; }
/// <summary>
/// Gets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public MqttTopicFilter TopicFilter { get; }
public MqttTopicFilter TopicFilter { get; internal set; }
}
}
@@ -1,24 +1,37 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public class MqttServerClientSubscribedHandlerDelegate : IMqttServerClientSubscribedTopicHandler
[Obsolete("Use MqttServerClientSubscribedTopicHandlerDelegate instead. This will be removed in a future version.")]
public sealed class MqttServerClientSubscribedHandlerDelegate : MqttServerClientSubscribedTopicHandlerDelegate
{
private readonly Func<MqttServerClientSubscribedTopicEventArgs, Task> _handler;
public MqttServerClientSubscribedHandlerDelegate(Action<MqttServerClientSubscribedTopicEventArgs> handler) : base(handler)
{
}
public MqttServerClientSubscribedHandlerDelegate(Action<MqttServerClientSubscribedTopicEventArgs> handler)
public MqttServerClientSubscribedHandlerDelegate(Func<MqttServerClientSubscribedTopicEventArgs, Task> handler) : base(handler)
{
}
}
public class MqttServerClientSubscribedTopicHandlerDelegate : IMqttServerClientSubscribedTopicHandler
{
readonly Func<MqttServerClientSubscribedTopicEventArgs, Task> _handler;
public MqttServerClientSubscribedTopicHandlerDelegate(Action<MqttServerClientSubscribedTopicEventArgs> handler)
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
_handler = eventArgs =>
{
handler(eventArgs);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
public MqttServerClientSubscribedHandlerDelegate(Func<MqttServerClientSubscribedTopicEventArgs, Task> handler)
public MqttServerClientSubscribedTopicHandlerDelegate(Func<MqttServerClientSubscribedTopicEventArgs, Task> handler)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
}
@@ -2,24 +2,18 @@
namespace MQTTnet.Server
{
public class MqttServerClientUnsubscribedTopicEventArgs : EventArgs
public sealed class MqttServerClientUnsubscribedTopicEventArgs : EventArgs
{
public MqttServerClientUnsubscribedTopicEventArgs(string clientId, string topicFilter)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
public string ClientId { get; internal set; }
/// <summary>
/// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public string TopicFilter { get; }
public string TopicFilter { get; internal set; }
}
}
@@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public class MqttServerClientUnsubscribedTopicHandlerDelegate : IMqttServerClientUnsubscribedTopicHandler
public sealed class MqttServerClientUnsubscribedTopicHandlerDelegate : IMqttServerClientUnsubscribedTopicHandler
{
private readonly Func<MqttServerClientUnsubscribedTopicEventArgs, Task> _handler;
readonly Func<MqttServerClientUnsubscribedTopicEventArgs, Task> _handler;
public MqttServerClientUnsubscribedTopicHandlerDelegate(Action<MqttServerClientUnsubscribedTopicEventArgs> handler)
{
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_handler = eventArgs =>
{
handler(eventArgs);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
@@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public class MqttServerConnectionValidatorDelegate : IMqttServerConnectionValidator
public sealed class MqttServerConnectionValidatorDelegate : IMqttServerConnectionValidator
{
private readonly Func<MqttConnectionValidatorContext, Task> _callback;
readonly Func<MqttConnectionValidatorContext, Task> _callback;
public MqttServerConnectionValidatorDelegate(Action<MqttConnectionValidatorContext> callback)
{
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_callback = context =>
{
callback(context);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
@@ -17,7 +17,7 @@ namespace MQTTnet.Server
_callback = context =>
{
callback(context);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
@@ -1,4 +1,5 @@
using System;
using MQTTnet.Server.Internal;
namespace MQTTnet.Server
{
@@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public class MqttServerStartedHandlerDelegate : IMqttServerStartedHandler
public sealed class MqttServerStartedHandlerDelegate : IMqttServerStartedHandler
{
private readonly Func<EventArgs, Task> _handler;
readonly Func<EventArgs, Task> _handler;
public MqttServerStartedHandlerDelegate(Action<EventArgs> handler)
{
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_handler = eventArgs =>
{
handler(eventArgs);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
@@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public class MqttServerStoppedHandlerDelegate : IMqttServerStoppedHandler
public sealed class MqttServerStoppedHandlerDelegate : IMqttServerStoppedHandler
{
private readonly Func<EventArgs, Task> _handler;
readonly Func<EventArgs, Task> _handler;
public MqttServerStoppedHandlerDelegate(Action<EventArgs> handler)
{
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_handler = eventArgs =>
{
handler(eventArgs);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
@@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public class MqttServerSubscriptionInterceptorDelegate : IMqttServerSubscriptionInterceptor
public sealed class MqttServerSubscriptionInterceptorDelegate : IMqttServerSubscriptionInterceptor
{
private readonly Func<MqttSubscriptionInterceptorContext, Task> _callback;
readonly Func<MqttSubscriptionInterceptorContext, Task> _callback;
public MqttServerSubscriptionInterceptorDelegate(Action<MqttSubscriptionInterceptorContext> callback)
{
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_callback = context =>
{
callback(context);
return Task.FromResult(0);
return PlatformAbstractionLayer.CompletedTask;
};
}
@@ -2,20 +2,13 @@
namespace MQTTnet.Server
{
public class MqttSubscriptionInterceptorContext
public sealed class MqttSubscriptionInterceptorContext
{
public MqttSubscriptionInterceptorContext(string clientId, MqttTopicFilter topicFilter, IDictionary<object, object> sessionItems)
{
ClientId = clientId;
TopicFilter = topicFilter;
SessionItems = sessionItems;
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
public string ClientId { get; internal set; }
/// <summary>
/// Gets or sets the topic filter.
@@ -26,7 +19,7 @@ namespace MQTTnet.Server
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary<object, object> SessionItems { get; }
public IDictionary<object, object> SessionItems { get; internal set; }
public bool AcceptSubscription { get; set; } = true;
@@ -2,32 +2,25 @@
namespace MQTTnet.Server
{
public class MqttUnsubscriptionInterceptorContext
public sealed class MqttUnsubscriptionInterceptorContext
{
public MqttUnsubscriptionInterceptorContext(string clientId, string topic, IDictionary<object, object> sessionItems)
{
ClientId = clientId;
Topic = topic;
SessionItems = sessionItems;
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
public string ClientId { get; internal set; }
/// <summary>
/// Gets or sets the MQTT topic.
/// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected client.
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator).
/// </summary>
public string Topic { get; set; }
public string Topic { get; internal set; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary<object, object> SessionItems { get; }
public IDictionary<object, object> SessionItems { get; internal set; }
public bool AcceptUnsubscription { get; set; } = true;
@@ -1,9 +0,0 @@
namespace MQTTnet.Server
{
public class PrepareClientSessionResult
{
public bool IsExistingSession { get; set; }
public MqttClientConnection Session { get; set; }
}
}
@@ -16,8 +16,12 @@ namespace MQTTnet.Server.Status
MqttProtocolVersion ProtocolVersion { get; }
DateTime ConnectedTimestamp { get; set; }
DateTime LastPacketReceivedTimestamp { get; }
DateTime LastPacketSentTimestamp { get; set; }
DateTime LastNonKeepAlivePacketReceivedTimestamp { get; }
long ReceivedApplicationMessagesCount { get; }
@@ -11,6 +11,9 @@ namespace MQTTnet.Server.Status
/// </summary>
string ClientId { get; }
/// <summary>
/// Gets the count of messages which are not yet sent to the client but already queued.
/// </summary>
long PendingApplicationMessagesCount { get; }
IDictionary<object, object> Items { get; }
@@ -2,6 +2,7 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Server.Internal;
namespace MQTTnet.Server.Status
{
@@ -24,9 +25,11 @@ namespace MQTTnet.Server.Status
public MqttProtocolVersion ProtocolVersion { get; set; }
public DateTime LastPacketReceivedTimestamp { get; set; }
public DateTime ConnectedTimestamp { get; set; }
public DateTime LastPacketReceivedTimestamp { get; set; }
public DateTime LastPacketSentTimestamp { get; set; }
public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; set; }
@@ -43,7 +46,7 @@ namespace MQTTnet.Server.Status
public long BytesSent { get; set; }
public long BytesReceived { get; set; }
public Task DisconnectAsync()
{
return _connection.StopAsync(MqttClientDisconnectReason.NormalDisconnection);
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Server.Internal;
namespace MQTTnet.Server.Status
{
@@ -25,8 +26,11 @@ namespace MQTTnet.Server.Status
public DateTime CreatedTimestamp { get; set; }
/// <summary>
/// This items can be used by the library user in order to store custom information.
/// </summary>
public IDictionary<object, object> Items { get; set; }
public Task DeleteAsync()
{
return _sessionsManager.DeleteSessionAsync(ClientId);
@@ -2,6 +2,7 @@
using BenchmarkDotNet.Jobs;
using MQTTnet.Server;
using System;
using MQTTnet.Server.Internal;
namespace MQTTnet.Benchmarks
{
+22
View File
@@ -0,0 +1,22 @@
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests
{
public abstract class BaseTestClass
{
public TestContext TestContext { get; set; }
protected TestEnvironment CreateTestEnvironment()
{
return new TestEnvironment(TestContext);
}
protected Task LongDelay()
{
return Task.Delay(TimeSpan.FromSeconds(1));
}
}
}
@@ -16,7 +16,7 @@ using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.MQTTv5
{
[TestClass]
public class Client_Tests
public sealed class Client_Tests
{
public TestContext TestContext { get; set; }
@@ -60,6 +60,7 @@ namespace MQTTnet.Tests.MQTTv5
Assert.AreEqual(2, receivedMessage.UserProperties.Count);
}
}
[TestMethod]
public async Task Connect_With_AssignedClientId()
{
@@ -116,7 +117,6 @@ namespace MQTTnet.Tests.MQTTv5
Assert.AreEqual("test123", serverConnectedClientId);
Assert.AreEqual("test123", serverDisconnectedClientId);
Assert.AreEqual("test123", clientAssignedClientId);
}
}
@@ -165,7 +165,6 @@ namespace MQTTnet.Tests.MQTTv5
Assert.AreEqual(1, result.Items.Count);
Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS1, result.Items[0].ResultCode);
}
}
@@ -172,21 +172,25 @@ namespace MQTTnet.Tests.Mockups
return StartServerAsync(new MqttServerOptionsBuilder());
}
public async Task<IMqttServer> StartServerAsync(MqttServerOptionsBuilder options)
public IMqttServer CreateServer()
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (Server != null)
{
throw new InvalidOperationException("Server already started.");
}
Server = new TestServerWrapper(_mqttFactory.CreateMqttServer(ServerLogger), TestContext, this);
return Server;
}
public async Task<IMqttServer> StartServerAsync(MqttServerOptionsBuilder options)
{
CreateServer();
options.WithDefaultEndpointPort(ServerPort);
options.WithMaxPendingMessagesPerClient(int.MaxValue);
await Server.StartAsync(options.Build()).ConfigureAwait(false);
await Server.StartAsync(options.Build());
return Server;
}
+1 -1
View File
@@ -50,7 +50,7 @@ namespace MQTTnet.Tests
Topic = "Test"
}, CancellationToken.None);
await Task.Delay(1000);
await Task.Delay(500);
// This will simulate a device which closes the connection directly
// after sending the data so do delay is added between send and dispose!
@@ -5,6 +5,7 @@ using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using MQTTnet.Server.Internal;
namespace MQTTnet.Tests
{
@@ -0,0 +1,186 @@
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Client.Receiving;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests
{
[TestClass]
public sealed class Server_Events_Tests : BaseTestClass
{
[TestMethod]
public async Task Fire_Client_Connected_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
MqttServerClientConnectedEventArgs eventArgs = null;
server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(e =>
{
eventArgs = e;
});
await testEnvironment.ConnectClientAsync(o => o.WithCredentials("TheUser"));
await LongDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Connected_Event)));
Assert.IsTrue(eventArgs.Endpoint.Contains("::1"));
Assert.AreEqual(MqttProtocolVersion.V311, eventArgs.ProtocolVersion);
Assert.AreEqual("TheUser", eventArgs.UserName);
}
}
[TestMethod]
public async Task Fire_Client_Disconnected_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
MqttServerClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedHandler = new MqttServerClientDisconnectedHandlerDelegate(e =>
{
eventArgs = e;
});
var client = await testEnvironment.ConnectClientAsync(o => o.WithCredentials("TheUser"));
await client.DisconnectAsync();
await LongDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Disconnected_Event)));
Assert.IsTrue(eventArgs.Endpoint.Contains("::1"));
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
}
}
[TestMethod]
public async Task Fire_Client_Subscribed_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
MqttServerClientSubscribedTopicEventArgs eventArgs = null;
server.ClientSubscribedTopicHandler = new MqttServerClientSubscribedTopicHandlerDelegate(e =>
{
eventArgs = e;
});
var client = await testEnvironment.ConnectClientAsync();
await client.SubscribeAsync("The/Topic", MqttQualityOfServiceLevel.AtLeastOnce);
await LongDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Subscribed_Event)));
Assert.AreEqual("The/Topic", eventArgs.TopicFilter.Topic);
Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, eventArgs.TopicFilter.QualityOfServiceLevel);
}
}
[TestMethod]
public async Task Fire_Client_Unsubscribed_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
MqttServerClientUnsubscribedTopicEventArgs eventArgs = null;
server.ClientUnsubscribedTopicHandler = new MqttServerClientUnsubscribedTopicHandlerDelegate(e =>
{
eventArgs = e;
});
var client = await testEnvironment.ConnectClientAsync();
await client.UnsubscribeAsync("The/Topic");
await LongDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Unsubscribed_Event)));
Assert.AreEqual("The/Topic", eventArgs.TopicFilter);
}
}
[TestMethod]
public async Task Fire_Application_Message_Received_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
MqttApplicationMessageReceivedEventArgs eventArgs = null;
server.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e =>
{
eventArgs = e;
});
var client = await testEnvironment.ConnectClientAsync();
await client.PublishAsync("The_Topic", "The_Payload");
await LongDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Application_Message_Received_Event)));
Assert.AreEqual("The_Topic", eventArgs.ApplicationMessage.Topic);
Assert.AreEqual("The_Payload", eventArgs.ApplicationMessage.ConvertPayloadToString());
}
}
[TestMethod]
public async Task Fire_Started_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = testEnvironment.CreateServer();
EventArgs eventArgs = null;
server.StartedHandler = new MqttServerStartedHandlerDelegate(e =>
{
eventArgs = e;
});
await server.StartAsync(new MqttServerOptionsBuilder().Build());
await LongDelay();
Assert.IsNotNull(eventArgs);
}
}
[TestMethod]
public async Task Fire_Stopped_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
EventArgs eventArgs = null;
server.StoppedHandler = new MqttServerStoppedHandlerDelegate(e =>
{
eventArgs = e;
});
await server.StopAsync();
await LongDelay();
Assert.IsNotNull(eventArgs);
}
}
}
}
@@ -10,10 +10,8 @@ using MQTTnet.Server;
namespace MQTTnet.Tests
{
[TestClass]
public class Server_Status_Tests
public sealed class Server_Status_Tests : BaseTestClass
{
public TestContext TestContext { get; set; }
[TestMethod]
public async Task Show_Client_And_Session_Statistics()
{
@@ -143,7 +141,7 @@ namespace MQTTnet.Tests
// At most once will send one packet to the client and the server will reply
// with an additional ACK packet.
await c1.PublishAsync("a", string.Empty, MqttQualityOfServiceLevel.AtLeastOnce);
await Task.Delay(50);
await Task.Delay(250);
var clientStatus = await server.GetClientStatusAsync();
+42 -5
View File
@@ -147,7 +147,7 @@ namespace MQTTnet.Tests
}
[TestMethod]
public async Task Will_Message_Do_Not_Send()
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
@@ -155,7 +155,7 @@ namespace MQTTnet.Tests
await testEnvironment.StartServerAsync();
var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build();
var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").Build();
var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage);
@@ -171,6 +171,38 @@ namespace MQTTnet.Tests
Assert.AreEqual(0, receivedMessagesCount);
}
}
[TestMethod]
public async Task Will_Message_Do_Not_Send_On_Takeover()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var receivedMessagesCount = 0;
await testEnvironment.StartServerAsync();
// C1 will receive the last will!
var c1 = await testEnvironment.ConnectClientAsync();
c1.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(c => Interlocked.Increment(ref receivedMessagesCount));
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
// C2 has the last will defined.
var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").Build();
var clientOptions = new MqttClientOptionsBuilder()
.WithWillMessage(willMessage)
.WithClientId("WillOwner");
var c2 = await testEnvironment.ConnectClientAsync(clientOptions);
// C3 will do the connection takeover.
var c3 = await testEnvironment.ConnectClientAsync(clientOptions);
await Task.Delay(1000);
Assert.AreEqual(0, receivedMessagesCount);
}
}
[TestMethod]
public async Task Will_Message_Send()
@@ -571,7 +603,12 @@ namespace MQTTnet.Tests
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServerAsync();
server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(e => server.SubscribeAsync(e.ClientId, "topic1"));
server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(async e =>
{
// Every client will automatically subscribe to this topic.
await server.SubscribeAsync(e.ClientId, "topic1");
});
var client = await testEnvironment.ConnectClientAsync();
var receivedMessages = new List<MqttApplicationMessage>();
@@ -636,11 +673,11 @@ namespace MQTTnet.Tests
Assert.AreEqual(1, clientConnectedCalled);
Assert.AreEqual(0, clientDisconnectedCalled);
await Task.Delay(500);
await Task.Delay(1000);
await c1.DisconnectAsync();
await Task.Delay(500);
await Task.Delay(1000);
Assert.AreEqual(1, clientConnectedCalled);
Assert.AreEqual(1, clientDisconnectedCalled);
@@ -1,5 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Server;
using MQTTnet.Server.Internal;
namespace MQTTnet.Tests
{
+5
View File
@@ -31,6 +31,7 @@ namespace MQTTnet.TestApp.NetCore
Console.WriteLine("a = Start QoS 2 benchmark");
Console.WriteLine("b = Start QoS 1 benchmark");
Console.WriteLine("c = Start QoS 0 benchmark");
Console.WriteLine("d = Start server with logging");
var pressedKey = Console.ReadKey(true);
if (pressedKey.KeyChar == '1')
@@ -83,6 +84,10 @@ namespace MQTTnet.TestApp.NetCore
{
Task.Run(PerformanceTest.RunQoS0Test);
}
else if (pressedKey.KeyChar == 'd')
{
Task.Run(ServerTest.RunEmptyServerWithLogging);
}
Thread.Sleep(Timeout.Infinite);
}
@@ -2,8 +2,10 @@
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Server.Internal;
namespace MQTTnet.TestApp.NetCore
{
@@ -13,6 +15,19 @@ namespace MQTTnet.TestApp.NetCore
{
var mqttServer = new MqttFactory().CreateMqttServer();
mqttServer.StartAsync(new MqttServerOptions()).GetAwaiter().GetResult();
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
}
public static void RunEmptyServerWithLogging()
{
var logger = new MqttNetLogger();
MqttNetConsoleLogger.ForwardToConsole(logger);
var mqttFactory = new MqttFactory(logger);
var mqttServer = mqttFactory.CreateMqttServer();
mqttServer.StartAsync(new MqttServerOptions()).GetAwaiter().GetResult();
Console.WriteLine("Press any key to exit.");
Console.ReadLine();