mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-20 11:45:43 +08:00
Improve internal server connection management. (#1232)
This commit is contained in:
@@ -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>
|
<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>
|
<releaseNotes>
|
||||||
* [ManagedClient] Extended ReconnectAsync (thanks to @nvsnkv, #1202).
|
* [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.Server] Moved server project to a dedicated GitHub repository.
|
||||||
* [MQTTnet, MQTTnet.Extensions.ManagedClient] Fixed bug that allowed invalid subscriptions (Thanks to @marcelwinh).
|
* [MQTTnet, MQTTnet.Extensions.ManagedClient] Fixed bug that allowed invalid subscriptions (Thanks to @marcelwinh).
|
||||||
Git commit: $gitCommit
|
Git commit: $gitCommit
|
||||||
|
|||||||
@@ -363,9 +363,9 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
|
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
|
finally
|
||||||
{
|
{
|
||||||
@@ -439,7 +439,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
if (exception is MqttCommunicationTimedOutException)
|
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;
|
throw;
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ namespace MQTTnet.Client.Options
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithCredentials(string username, string password = null)
|
public MqttClientOptionsBuilder WithCredentials(string username, string password)
|
||||||
{
|
{
|
||||||
byte[] passwordBuffer = null;
|
byte[] passwordBuffer = null;
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ namespace MQTTnet.Client.Options
|
|||||||
return WithCredentials(username, passwordBuffer);
|
return WithCredentials(username, passwordBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithCredentials(string username, byte[] password = null)
|
public MqttClientOptionsBuilder WithCredentials(string username, byte[] password)
|
||||||
{
|
{
|
||||||
_options.Credentials = new MqttClientCredentials
|
_options.Credentials = new MqttClientCredentials
|
||||||
{
|
{
|
||||||
@@ -155,6 +155,16 @@ namespace MQTTnet.Client.Options
|
|||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithCredentials(string username)
|
||||||
|
{
|
||||||
|
_options.Credentials = new MqttClientCredentials
|
||||||
|
{
|
||||||
|
Username = username
|
||||||
|
};
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithCredentials(IMqttClientCredentials credentials)
|
public MqttClientOptionsBuilder WithCredentials(IMqttClientCredentials credentials)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Client.Receiving
|
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)
|
public MqttApplicationMessageReceivedHandlerDelegate(Action<MqttApplicationMessageReceivedEventArgs> handler)
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ namespace MQTTnet.Client.Receiving
|
|||||||
_handler = context =>
|
_handler = context =>
|
||||||
{
|
{
|
||||||
handler(context);
|
handler(context);
|
||||||
return Task.FromResult(0);
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using MQTTnet.Client.Unsubscribing;
|
|||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using MqttClientSubscribeResult = MQTTnet.Client.Subscribing.MqttClientSubscribeResult;
|
|
||||||
|
|
||||||
namespace MQTTnet.Formatter
|
namespace MQTTnet.Formatter
|
||||||
{
|
{
|
||||||
@@ -22,7 +21,7 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
MqttClientPublishResult CreateClientPublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket);
|
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);
|
MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace MQTTnet
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Consider using struct instead.
|
||||||
public class MqttTopicFilter
|
public class MqttTopicFilter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
public enum MqttRetainHandling
|
public enum MqttRetainHandling
|
||||||
{
|
{
|
||||||
SendAtSubscribe = 0,
|
SendAtSubscribe = 0,
|
||||||
|
|
||||||
SendAtSubscribeIfNewSubscriptionOnly = 1,
|
SendAtSubscribeIfNewSubscriptionOnly = 1,
|
||||||
|
|
||||||
DoNotSendOnSubscribe = 2
|
DoNotSendOnSubscribe = 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server.Internal
|
||||||
{
|
{
|
||||||
public struct CheckSubscriptionsResult
|
public struct CheckSubscriptionsResult
|
||||||
{
|
{
|
||||||
+241
-261
@@ -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;
|
||||||
|
using MQTTnet.Client.Disconnecting;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Implementations;
|
using MQTTnet.Implementations;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.PacketDispatcher;
|
using MQTTnet.PacketDispatcher;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server.Status;
|
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
|
public sealed class MqttClientConnection : IDisposable
|
||||||
{
|
{
|
||||||
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
|
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
|
||||||
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
||||||
readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource();
|
|
||||||
|
|
||||||
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
|
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
|
||||||
readonly MqttClientSessionsManager _sessionsManager;
|
readonly MqttClientSessionsManager _sessionsManager;
|
||||||
|
|
||||||
|
readonly IMqttChannelAdapter _channelAdapter;
|
||||||
readonly IMqttNetScopedLogger _logger;
|
readonly IMqttNetScopedLogger _logger;
|
||||||
readonly IMqttServerOptions _serverOptions;
|
readonly IMqttServerOptions _serverOptions;
|
||||||
|
|
||||||
readonly IMqttChannelAdapter _channelAdapter;
|
|
||||||
readonly MqttConnectionValidatorContext _connectionValidatorContext;
|
|
||||||
readonly IMqttDataConverter _dataConverter;
|
|
||||||
readonly string _endpoint;
|
readonly string _endpoint;
|
||||||
readonly DateTime _connectedTimestamp;
|
|
||||||
|
|
||||||
volatile Task _packageReceiverTask;
|
CancellationTokenSource _cancellationToken;
|
||||||
DateTime _lastNonKeepAlivePacketReceivedTimestamp;
|
|
||||||
|
|
||||||
long _receivedPacketsCount;
|
public MqttClientConnection(
|
||||||
long _sentPacketsCount = 1; // Start with 1 because the CONNECT packet is not counted anywhere.
|
MqttConnectPacket connectPacket,
|
||||||
long _receivedApplicationMessagesCount;
|
|
||||||
long _sentApplicationMessagesCount;
|
|
||||||
MqttClientDisconnectReason _disconnectReason;
|
|
||||||
|
|
||||||
public MqttClientConnection(MqttConnectPacket connectPacket,
|
|
||||||
IMqttChannelAdapter channelAdapter,
|
IMqttChannelAdapter channelAdapter,
|
||||||
MqttClientSession session,
|
MqttClientSession session,
|
||||||
MqttConnectionValidatorContext connectionValidatorContext,
|
|
||||||
IMqttServerOptions serverOptions,
|
IMqttServerOptions serverOptions,
|
||||||
MqttClientSessionsManager sessionsManager,
|
MqttClientSessionsManager sessionsManager,
|
||||||
IMqttRetainedMessagesManager retainedMessagesManager,
|
IMqttRetainedMessagesManager retainedMessagesManager,
|
||||||
IMqttNetLogger logger)
|
IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
Session = session ?? throw new ArgumentNullException(nameof(session));
|
|
||||||
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
||||||
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
|
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
|
||||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
||||||
|
|
||||||
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
|
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
|
||||||
_connectionValidatorContext = connectionValidatorContext ?? throw new ArgumentNullException(nameof(connectionValidatorContext));
|
_endpoint = channelAdapter.Endpoint;
|
||||||
_dataConverter = _channelAdapter.PacketFormatterAdapter.DataConverter;
|
|
||||||
_endpoint = _channelAdapter.Endpoint;
|
Session = session ?? throw new ArgumentNullException(nameof(session));
|
||||||
ConnectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
ConnectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||||
|
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||||
_logger = logger.CreateScopedLogger(nameof(MqttClientConnection));
|
_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 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 MqttClientSession Session { get; }
|
||||||
|
|
||||||
|
public bool IsTakenOver { get; set; }
|
||||||
|
|
||||||
|
public bool IsCleanDisconnect { get; private set; }
|
||||||
|
|
||||||
public async Task StopAsync(MqttClientDisconnectReason reason)
|
public async Task StopAsync(MqttClientDisconnectReason reason)
|
||||||
{
|
{
|
||||||
Status = MqttClientConnectionStatus.Finalizing;
|
IsRunning = false;
|
||||||
_disconnectReason = reason;
|
|
||||||
|
|
||||||
if (reason == MqttClientDisconnectReason.SessionTakenOver || reason == MqttClientDisconnectReason.KeepAliveTimeout)
|
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 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
|
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
|
||||||
// at the client!
|
// at the client!
|
||||||
try
|
await TrySendDisconnectPacket(reason).ConfigureAwait(false);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StopInternal();
|
StopInternal();
|
||||||
|
|
||||||
await (_packageReceiverTask ?? PlatformAbstractionLayer.CompletedTask);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetStatistics()
|
public void ResetStatistics()
|
||||||
@@ -127,24 +95,16 @@ namespace MQTTnet.Server
|
|||||||
_channelAdapter.ResetStatistics();
|
_channelAdapter.ResetStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FillStatus(MqttClientStatus status)
|
public void FillClientStatus(MqttClientStatus clientStatus)
|
||||||
{
|
{
|
||||||
status.ClientId = ClientId;
|
clientStatus.ClientId = ClientId;
|
||||||
status.Endpoint = _endpoint;
|
clientStatus.Endpoint = _endpoint;
|
||||||
status.ProtocolVersion = _channelAdapter.PacketFormatterAdapter.ProtocolVersion;
|
|
||||||
|
|
||||||
status.ReceivedApplicationMessagesCount = Interlocked.Read(ref _receivedApplicationMessagesCount);
|
clientStatus.ProtocolVersion = _channelAdapter.PacketFormatterAdapter.ProtocolVersion;
|
||||||
status.SentApplicationMessagesCount = Interlocked.Read(ref _sentApplicationMessagesCount);
|
clientStatus.BytesSent = _channelAdapter.BytesSent;
|
||||||
|
clientStatus.BytesReceived = _channelAdapter.BytesReceived;
|
||||||
|
|
||||||
status.ReceivedPacketsCount = Interlocked.Read(ref _receivedPacketsCount);
|
Statistics.FillClientStatus(clientStatus);
|
||||||
status.SentPacketsCount = Interlocked.Read(ref _sentPacketsCount);
|
|
||||||
|
|
||||||
status.ConnectedTimestamp = _connectedTimestamp;
|
|
||||||
status.LastPacketReceivedTimestamp = LastPacketReceivedTimestamp;
|
|
||||||
status.LastNonKeepAlivePacketReceivedTimestamp = _lastNonKeepAlivePacketReceivedTimestamp;
|
|
||||||
|
|
||||||
status.BytesSent = _channelAdapter.BytesSent;
|
|
||||||
status.BytesReceived = _channelAdapter.BytesReceived;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -152,31 +112,60 @@ namespace MQTTnet.Server
|
|||||||
_cancellationToken.Dispose();
|
_cancellationToken.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task RunAsync()
|
public async Task RunAsync()
|
||||||
{
|
{
|
||||||
_packageReceiverTask = RunInternalAsync(_cancellationToken.Token);
|
_logger.Info("Client '{0}': Session started.", ClientId);
|
||||||
return _packageReceiverTask;
|
|
||||||
|
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
|
try
|
||||||
{
|
{
|
||||||
_logger.Info("Client '{0}': Session started.", ClientId);
|
// 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
|
||||||
Session.WillMessage = ConnectPacket.WillMessage;
|
// own exception in the reading loop!
|
||||||
|
|
||||||
await SendAsync(_channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(_connectionValidatorContext), cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
Task.Run(() => SendPendingPacketsAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
|
|
||||||
|
|
||||||
Session.IsCleanSession = false;
|
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
Status = MqttClientConnectionStatus.Running;
|
|
||||||
|
|
||||||
var packet = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
var packet = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||||
if (packet == null)
|
if (packet == null)
|
||||||
{
|
{
|
||||||
@@ -184,13 +173,7 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Increment(ref _sentPacketsCount);
|
Statistics.HandleReceivedPacket(packet);
|
||||||
LastPacketReceivedTimestamp = DateTime.UtcNow;
|
|
||||||
|
|
||||||
if (!(packet is MqttPingReqPacket || packet is MqttPingRespPacket))
|
|
||||||
{
|
|
||||||
_lastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet is MqttPublishPacket publishPacket)
|
if (packet is MqttPublishPacket publishPacket)
|
||||||
{
|
{
|
||||||
@@ -210,7 +193,8 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
else if (packet is MqttPingReqPacket)
|
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)
|
else if (packet is MqttPingRespPacket)
|
||||||
{
|
{
|
||||||
@@ -218,10 +202,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
else if (packet is MqttDisconnectPacket)
|
else if (packet is MqttDisconnectPacket)
|
||||||
{
|
{
|
||||||
Session.WillMessage = null;
|
IsCleanDisconnect = true;
|
||||||
disconnectType = MqttClientDisconnectType.Clean;
|
|
||||||
|
|
||||||
StopInternal();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -246,40 +227,111 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
_logger.Error(exception, "Client '{0}': Error while receiving client packets.", ClientId);
|
_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();
|
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()
|
void StopInternal()
|
||||||
{
|
{
|
||||||
_cancellationToken.Cancel();
|
_cancellationToken?.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<MqttTopicFilter> topicFilters)
|
async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<MqttTopicFilter> topicFilters)
|
||||||
@@ -294,7 +346,7 @@ namespace MQTTnet.Server
|
|||||||
Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var pubCompPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubCompPacket(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
var pubCompPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubCompPacket(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
return SendAsync(pubCompPacket, cancellationToken);
|
return SendPacketAsync(pubCompPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket, CancellationToken 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 subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket, ConnectPacket).ConfigureAwait(false);
|
||||||
var subAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateSubAckPacket(subscribePacket, subscribeResult);
|
var subAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateSubAckPacket(subscribePacket, subscribeResult);
|
||||||
|
|
||||||
await SendAsync(subAckPacket, cancellationToken).ConfigureAwait(false);
|
await SendPacketAsync(subAckPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (subscribeResult.CloseConnection)
|
if (subscribeResult.CloseConnection)
|
||||||
{
|
{
|
||||||
@@ -318,38 +370,36 @@ namespace MQTTnet.Server
|
|||||||
var reasonCodes = await Session.SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
|
var reasonCodes = await Session.SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
|
||||||
var unsubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateUnsubAckPacket(unsubscribePacket, reasonCodes);
|
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)
|
Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Interlocked.Increment(ref _sentApplicationMessagesCount);
|
|
||||||
|
|
||||||
HandleTopicAlias(publishPacket);
|
HandleTopicAlias(publishPacket);
|
||||||
|
|
||||||
var applicationMessage = _dataConverter.CreateApplicationMessage(publishPacket);
|
var applicationMessage = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket);
|
||||||
_sessionsManager.DispatchApplicationMessage(applicationMessage, this);
|
_sessionsManager.DispatchApplicationMessage(applicationMessage, this);
|
||||||
|
|
||||||
switch (publishPacket.QualityOfServiceLevel)
|
switch (publishPacket.QualityOfServiceLevel)
|
||||||
{
|
{
|
||||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||||
{
|
{
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
}
|
}
|
||||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||||
{
|
{
|
||||||
var pubAckPacket = _dataConverter.CreatePubAckPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
var pubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubAckPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
return SendAsync(pubAckPacket, cancellationToken);
|
return SendPacketAsync(pubAckPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||||
{
|
{
|
||||||
var pubRecPacket = _dataConverter.CreatePubRecPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
var pubRecPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubRecPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
return SendAsync(pubRecPacket, cancellationToken);
|
return SendPacketAsync(pubRecPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
default:
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task SendPendingPacketsAsync(CancellationToken cancellationToken)
|
async Task TrySendDisconnectPacket(MqttClientDisconnectReason reason)
|
||||||
{
|
{
|
||||||
MqttQueuedApplicationMessage queuedApplicationMessage = null;
|
|
||||||
MqttPublishPacket publishPacket = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
var disconnectOptions = new MqttClientDisconnectOptions
|
||||||
{
|
{
|
||||||
queuedApplicationMessage = await Session.ApplicationMessagesQueue.DequeueAsync(cancellationToken).ConfigureAwait(false);
|
ReasonCode = reason,
|
||||||
if (queuedApplicationMessage == null)
|
ReasonString = reason.ToString()
|
||||||
{
|
};
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
var disconnectPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateDisconnectPacket(disconnectOptions);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
publishPacket = _dataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage);
|
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
|
||||||
publishPacket.QualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel;
|
{
|
||||||
|
await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
if (exception is OperationCanceledException)
|
_logger.Warning(exception, "Client '{{0}}': Error while sending DISCONNECT packet (Reason = {1}).", ClientId, reason);
|
||||||
{
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
var context = new MqttClientMessageQueueInterceptorContext
|
||||||
{
|
{
|
||||||
Interlocked.Increment(ref _receivedApplicationMessagesCount);
|
SenderClientId = queuedApplicationMessage.SenderClientId,
|
||||||
}
|
ReceiverClientId = ClientId,
|
||||||
}, cancellationToken);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-5
@@ -1,10 +1,10 @@
|
|||||||
using MQTTnet.Diagnostics;
|
using System;
|
||||||
using MQTTnet.Server.Status;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Server.Status;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server.Internal
|
||||||
{
|
{
|
||||||
public sealed class MqttClientSession
|
public sealed class MqttClientSession
|
||||||
{
|
{
|
||||||
@@ -81,7 +81,7 @@ namespace MQTTnet.Server
|
|||||||
return SubscriptionsManager.UnsubscribeAsync(topicFilters);
|
return SubscriptionsManager.UnsubscribeAsync(topicFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FillStatus(MqttSessionStatus status)
|
public void FillSessionStatus(MqttSessionStatus status)
|
||||||
{
|
{
|
||||||
status.ClientId = ClientId;
|
status.ClientId = ClientId;
|
||||||
status.CreatedTimestamp = _createdTimestamp;
|
status.CreatedTimestamp = _createdTimestamp;
|
||||||
+4
-4
@@ -1,10 +1,10 @@
|
|||||||
using MQTTnet.Internal;
|
using System;
|
||||||
using MQTTnet.Protocol;
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server.Internal
|
||||||
{
|
{
|
||||||
public sealed class MqttClientSessionApplicationMessagesQueue : IDisposable
|
public sealed class MqttClientSessionApplicationMessagesQueue : IDisposable
|
||||||
{
|
{
|
||||||
+118
-101
@@ -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.Adapter;
|
||||||
|
using MQTTnet.Client.Disconnecting;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
@@ -6,15 +13,8 @@ using MQTTnet.Internal;
|
|||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server.Status;
|
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
|
public sealed class MqttClientSessionsManager : IDisposable
|
||||||
{
|
{
|
||||||
@@ -53,53 +53,66 @@ namespace MQTTnet.Server
|
|||||||
Task.Run(() => TryProcessQueuedApplicationMessagesAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
|
Task.Run(() => TryProcessQueuedApplicationMessagesAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
|
async Task<MqttConnectPacket> ReceiveConnectPacket(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MqttConnectPacket connectPacket;
|
using (var timeoutToken = new CancellationTokenSource(_options.DefaultCommunicationTimeout))
|
||||||
try
|
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken.Token, cancellationToken))
|
||||||
{
|
{
|
||||||
using (var timeoutToken = new CancellationTokenSource(_options.DefaultCommunicationTimeout))
|
var firstPacket = await channelAdapter.ReceivePacketAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
// Nothing was received in time etc.
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (MqttCommunicationTimedOutException)
|
|
||||||
{
|
|
||||||
_logger.Warning(null, "Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var connectionValidatorContext = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false);
|
MqttConnAckPacket connAckPacket;
|
||||||
|
|
||||||
|
var connectionValidatorContext = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
|
||||||
if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success)
|
if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success)
|
||||||
{
|
{
|
||||||
// Send failure response here without preparing a session. The result for a successful connect
|
// Send failure response here without preparing a session!
|
||||||
// will be sent from the session itself.
|
connAckPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext);
|
||||||
var connAckPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext);
|
|
||||||
await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false);
|
await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var connection = await CreateClientConnectionAsync(connectPacket, connectionValidatorContext, channelAdapter).ConfigureAwait(false);
|
clientConnection = await CreateClientConnection(connectPacket, channelAdapter, connectionValidatorContext.SessionItems).ConfigureAwait(false);
|
||||||
await _eventDispatcher.SafeNotifyClientConnectedAsync(connectPacket.ClientId).ConfigureAwait(false);
|
|
||||||
await connection.RunAsync().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)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -108,6 +121,41 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
_logger.Error(exception, exception.Message);
|
_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()
|
public async Task CloseAllConnectionsAsync()
|
||||||
@@ -132,7 +180,7 @@ namespace MQTTnet.Server
|
|||||||
return _connections.Values.ToList();
|
return _connections.Values.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IList<IMqttClientStatus>> GetClientStatusAsync()
|
public Task<IList<IMqttClientStatus>> GetClientStatusAsync()
|
||||||
{
|
{
|
||||||
var result = new List<IMqttClientStatus>();
|
var result = new List<IMqttClientStatus>();
|
||||||
@@ -142,10 +190,10 @@ namespace MQTTnet.Server
|
|||||||
foreach (var connection in _connections.Values)
|
foreach (var connection in _connections.Values)
|
||||||
{
|
{
|
||||||
var clientStatus = new MqttClientStatus(connection);
|
var clientStatus = new MqttClientStatus(connection);
|
||||||
connection.FillStatus(clientStatus);
|
connection.FillClientStatus(clientStatus);
|
||||||
|
|
||||||
var sessionStatus = new MqttSessionStatus(connection.Session, this);
|
var sessionStatus = new MqttSessionStatus(connection.Session, this);
|
||||||
connection.Session.FillStatus(sessionStatus);
|
connection.Session.FillSessionStatus(sessionStatus);
|
||||||
clientStatus.Session = sessionStatus;
|
clientStatus.Session = sessionStatus;
|
||||||
|
|
||||||
result.Add(clientStatus);
|
result.Add(clientStatus);
|
||||||
@@ -164,7 +212,7 @@ namespace MQTTnet.Server
|
|||||||
foreach (var session in _sessions.Values)
|
foreach (var session in _sessions.Values)
|
||||||
{
|
{
|
||||||
var sessionStatus = new MqttSessionStatus(session, this);
|
var sessionStatus = new MqttSessionStatus(session, this);
|
||||||
session.FillStatus(sessionStatus);
|
session.FillSessionStatus(sessionStatus);
|
||||||
|
|
||||||
result.Add(sessionStatus);
|
result.Add(sessionStatus);
|
||||||
}
|
}
|
||||||
@@ -203,7 +251,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
_connections.TryGetValue(clientId, out connection);
|
_connections.TryGetValue(clientId, out connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_sessions)
|
lock (_sessions)
|
||||||
{
|
{
|
||||||
_sessions.Remove(clientId);
|
_sessions.Remove(clientId);
|
||||||
@@ -213,39 +261,10 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
await connection.StopAsync(MqttClientDisconnectReason.NormalDisconnection).ConfigureAwait(false);
|
await connection.StopAsync(MqttClientDisconnectReason.NormalDisconnection).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Verbose("Session for client '{0}' deleted.", clientId);
|
_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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_messageQueue?.Dispose();
|
_messageQueue?.Dispose();
|
||||||
@@ -278,7 +297,7 @@ namespace MQTTnet.Server
|
|||||||
MqttPendingApplicationMessage queuedApplicationMessage;
|
MqttPendingApplicationMessage queuedApplicationMessage;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
queuedApplicationMessage = _messageQueue.Take(cancellationToken);
|
queuedApplicationMessage = _messageQueue.Take(cancellationToken);
|
||||||
}
|
}
|
||||||
catch (ArgumentNullException)
|
catch (ArgumentNullException)
|
||||||
{
|
{
|
||||||
@@ -315,7 +334,7 @@ namespace MQTTnet.Server
|
|||||||
applicationMessage = interceptorContext.ApplicationMessage;
|
applicationMessage = interceptorContext.ApplicationMessage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _eventDispatcher.SafeNotifyApplicationMessageReceivedAsync(senderClientId, applicationMessage).ConfigureAwait(false);
|
await _eventDispatcher.SafeNotifyApplicationMessageReceivedAsync(senderClientId, applicationMessage).ConfigureAwait(false);
|
||||||
|
|
||||||
if (applicationMessage.Retain)
|
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;
|
var connectionValidator = _options.ConnectionValidator;
|
||||||
|
|
||||||
@@ -388,9 +410,8 @@ namespace MQTTnet.Server
|
|||||||
return context;
|
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;
|
MqttClientConnection connection;
|
||||||
|
|
||||||
using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false))
|
using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false))
|
||||||
@@ -401,14 +422,14 @@ namespace MQTTnet.Server
|
|||||||
if (!_sessions.TryGetValue(connectPacket.ClientId, out session))
|
if (!_sessions.TryGetValue(connectPacket.ClientId, out session))
|
||||||
{
|
{
|
||||||
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
|
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
|
||||||
session = CreateSession(connectPacket.ClientId, connectionValidatorContext);
|
session = CreateSession(connectPacket.ClientId, sessionItems);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (connectPacket.CleanSession)
|
if (connectPacket.CleanSession)
|
||||||
{
|
{
|
||||||
_logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId);
|
_logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId);
|
||||||
session = CreateSession(connectPacket.ClientId, connectionValidatorContext);
|
session = CreateSession(connectPacket.ClientId, sessionItems);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -419,16 +440,21 @@ namespace MQTTnet.Server
|
|||||||
_sessions[connectPacket.ClientId] = session;
|
_sessions[connectPacket.ClientId] = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MqttClientConnection existingConnection;
|
||||||
|
|
||||||
lock (_connections)
|
lock (_connections)
|
||||||
{
|
{
|
||||||
_connections.TryGetValue(connectPacket.ClientId, out existingConnection);
|
_connections.TryGetValue(connectPacket.ClientId, out existingConnection);
|
||||||
connection = CreateConnection(connectPacket, channelAdapter, session, connectionValidatorContext);
|
connection = CreateConnection(connectPacket, channelAdapter, session);
|
||||||
|
|
||||||
_connections[connectPacket.ClientId] = connection;
|
_connections[connectPacket.ClientId] = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingConnection != null)
|
if (existingConnection != null)
|
||||||
{
|
{
|
||||||
|
await _eventDispatcher.SafeNotifyClientDisconnectedAsync(existingConnection.ClientId, MqttClientDisconnectType.Takeover, existingConnection.Endpoint);
|
||||||
|
|
||||||
|
existingConnection.IsTakenOver = true;
|
||||||
await existingConnection.StopAsync(MqttClientDisconnectReason.SessionTakenOver).ConfigureAwait(false);
|
await existingConnection.StopAsync(MqttClientDisconnectReason.SessionTakenOver).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,7 +462,8 @@ namespace MQTTnet.Server
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<MqttApplicationMessageInterceptorContext> InterceptApplicationMessageAsync(IMqttServerApplicationMessageInterceptor interceptor, MqttClientConnection clientConnection, MqttApplicationMessage applicationMessage)
|
async Task<MqttApplicationMessageInterceptorContext> InterceptApplicationMessageAsync(IMqttServerApplicationMessageInterceptor interceptor, MqttClientConnection clientConnection,
|
||||||
|
MqttApplicationMessage applicationMessage)
|
||||||
{
|
{
|
||||||
string senderClientId;
|
string senderClientId;
|
||||||
IDictionary<object, object> sessionItems;
|
IDictionary<object, object> sessionItems;
|
||||||
@@ -453,29 +480,20 @@ namespace MQTTnet.Server
|
|||||||
sessionItems = clientConnection.Session.Items;
|
sessionItems = clientConnection.Session.Items;
|
||||||
}
|
}
|
||||||
|
|
||||||
var interceptorContext = new MqttApplicationMessageInterceptorContext(senderClientId, sessionItems, _logger)
|
var interceptorContext = new MqttApplicationMessageInterceptorContext
|
||||||
{
|
{
|
||||||
|
ClientId = senderClientId,
|
||||||
|
SessionItems = sessionItems,
|
||||||
|
Logger = _logger,
|
||||||
AcceptPublish = true,
|
AcceptPublish = true,
|
||||||
ApplicationMessage = applicationMessage,
|
ApplicationMessage = applicationMessage,
|
||||||
CloseConnection = false
|
CloseConnection = false
|
||||||
};
|
};
|
||||||
|
|
||||||
await interceptor.InterceptApplicationMessagePublishAsync(interceptorContext).ConfigureAwait(false);
|
await interceptor.InterceptApplicationMessagePublishAsync(interceptorContext).ConfigureAwait(false);
|
||||||
return interceptorContext;
|
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)
|
MqttClientSession GetSession(string clientId)
|
||||||
{
|
{
|
||||||
lock (_sessions)
|
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(
|
return new MqttClientConnection(
|
||||||
connectPacket,
|
connectPacket,
|
||||||
channelAdapter,
|
channelAdapter,
|
||||||
session,
|
session,
|
||||||
connectionValidatorContext,
|
|
||||||
_options,
|
_options,
|
||||||
this,
|
this,
|
||||||
_retainedMessagesManager,
|
_retainedMessagesManager,
|
||||||
_rootLogger);
|
_rootLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttClientSession CreateSession(string clientId, MqttConnectionValidatorContext connectionValidatorContext)
|
MqttClientSession CreateSession(string clientId, IDictionary<object, object> sessionItems)
|
||||||
{
|
{
|
||||||
return new MqttClientSession(
|
return new MqttClientSession(
|
||||||
clientId,
|
clientId,
|
||||||
connectionValidatorContext.SessionItems,
|
sessionItems,
|
||||||
_eventDispatcher,
|
_eventDispatcher,
|
||||||
_options,
|
_options,
|
||||||
_retainedMessagesManager,
|
_retainedMessagesManager,
|
||||||
+39
-24
@@ -1,12 +1,12 @@
|
|||||||
using MQTTnet.Packets;
|
using System;
|
||||||
using MQTTnet.Protocol;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server.Internal
|
||||||
{
|
{
|
||||||
public sealed class MqttClientSubscriptionsManager
|
public sealed class MqttClientSubscriptionsManager
|
||||||
{
|
{
|
||||||
@@ -37,9 +37,11 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
var interceptorContext = await InterceptSubscribeAsync(originalTopicFilter).ConfigureAwait(false);
|
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.ReturnCodes.Add(MqttSubscribeReturnCode.Failure);
|
||||||
result.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError);
|
result.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError);
|
||||||
@@ -50,12 +52,12 @@ namespace MQTTnet.Server
|
|||||||
result.ReasonCodes.Add(ConvertToSubscribeReasonCode(finalTopicFilter.QualityOfServiceLevel));
|
result.ReasonCodes.Add(ConvertToSubscribeReasonCode(finalTopicFilter.QualityOfServiceLevel));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interceptorContext.CloseConnection)
|
if (closeConnection)
|
||||||
{
|
{
|
||||||
result.CloseConnection = true;
|
result.CloseConnection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!interceptorContext.AcceptSubscription || string.IsNullOrEmpty(finalTopicFilter?.Topic))
|
if (!acceptSubscription || string.IsNullOrEmpty(finalTopicFilter?.Topic))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -83,12 +85,7 @@ namespace MQTTnet.Server
|
|||||||
foreach (var topicFilter in topicFilters)
|
foreach (var topicFilter in topicFilters)
|
||||||
{
|
{
|
||||||
var interceptorContext = await InterceptSubscribeAsync(topicFilter).ConfigureAwait(false);
|
var interceptorContext = await InterceptSubscribeAsync(topicFilter).ConfigureAwait(false);
|
||||||
if (!interceptorContext.AcceptSubscription)
|
if (interceptorContext != null && !interceptorContext.AcceptSubscription)
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!interceptorContext.AcceptSubscription)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -116,7 +113,7 @@ namespace MQTTnet.Server
|
|||||||
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
||||||
{
|
{
|
||||||
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
|
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
|
||||||
if (!interceptorContext.AcceptUnsubscription)
|
if (interceptorContext != null && !interceptorContext.AcceptUnsubscription)
|
||||||
{
|
{
|
||||||
reasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError);
|
reasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError);
|
||||||
continue;
|
continue;
|
||||||
@@ -150,7 +147,7 @@ namespace MQTTnet.Server
|
|||||||
foreach (var topicFilter in topicFilters)
|
foreach (var topicFilter in topicFilters)
|
||||||
{
|
{
|
||||||
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
|
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
|
||||||
if (!interceptorContext.AcceptUnsubscription)
|
if (interceptorContext != null && !interceptorContext.AcceptUnsubscription)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -187,7 +184,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
qosLevels.Add(subscription.Value.QualityOfServiceLevel);
|
qosLevels.Add(subscription.Value.QualityOfServiceLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,23 +208,41 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
async Task<MqttSubscriptionInterceptorContext> InterceptSubscribeAsync(MqttTopicFilter topicFilter)
|
async Task<MqttSubscriptionInterceptorContext> InterceptSubscribeAsync(MqttTopicFilter topicFilter)
|
||||||
{
|
{
|
||||||
var context = new MqttSubscriptionInterceptorContext(_clientSession.ClientId, topicFilter, _clientSession.Items);
|
var interceptor = _options.SubscriptionInterceptor;
|
||||||
if (_options.SubscriptionInterceptor != null)
|
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;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribeAsync(string topicFilter)
|
async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribeAsync(string topicFilter)
|
||||||
{
|
{
|
||||||
var context = new MqttUnsubscriptionInterceptorContext(_clientSession.ClientId, topicFilter, _clientSession.Items);
|
var interceptor = _options.UnsubscriptionInterceptor;
|
||||||
if (_options.UnsubscriptionInterceptor != null)
|
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;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,4 +269,4 @@ namespace MQTTnet.Server
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+4
-4
@@ -1,13 +1,13 @@
|
|||||||
using MQTTnet.Diagnostics;
|
using System;
|
||||||
using MQTTnet.Implementations;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server.Internal
|
||||||
{
|
{
|
||||||
public sealed class MqttRetainedMessagesManager : IMqttRetainedMessagesManager
|
public sealed class MqttRetainedMessagesManager : IMqttRetainedMessagesManager
|
||||||
{
|
{
|
||||||
+29
-8
@@ -1,9 +1,11 @@
|
|||||||
using MQTTnet.Client.Receiving;
|
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
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
|
public sealed class MqttServerEventDispatcher
|
||||||
{
|
{
|
||||||
@@ -26,7 +28,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; }
|
public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; }
|
||||||
|
|
||||||
public async Task SafeNotifyClientConnectedAsync(string clientId)
|
public async Task SafeNotifyClientConnectedAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -36,7 +38,13 @@ namespace MQTTnet.Server
|
|||||||
return;
|
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)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
@@ -54,7 +62,12 @@ namespace MQTTnet.Server
|
|||||||
return;
|
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)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
@@ -72,7 +85,11 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await handler.HandleClientSubscribedTopicAsync(new MqttServerClientSubscribedTopicEventArgs(clientId, topicFilter)).ConfigureAwait(false);
|
await handler.HandleClientSubscribedTopicAsync(new MqttServerClientSubscribedTopicEventArgs
|
||||||
|
{
|
||||||
|
ClientId = clientId,
|
||||||
|
TopicFilter = topicFilter
|
||||||
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
@@ -90,7 +107,11 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await handler.HandleClientUnsubscribedTopicAsync(new MqttServerClientUnsubscribedTopicEventArgs(clientId, topicFilter)).ConfigureAwait(false);
|
await handler.HandleClientUnsubscribedTopicAsync(new MqttServerClientUnsubscribedTopicEventArgs
|
||||||
|
{
|
||||||
|
ClientId = clientId,
|
||||||
|
TopicFilter = topicFilter
|
||||||
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
+7
-7
@@ -1,12 +1,12 @@
|
|||||||
using MQTTnet.Diagnostics;
|
using System;
|
||||||
using MQTTnet.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Client.Disconnecting;
|
using MQTTnet.Client.Disconnecting;
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Implementations;
|
using MQTTnet.Implementations;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server.Internal
|
||||||
{
|
{
|
||||||
public sealed class MqttServerKeepAliveMonitor
|
public sealed class MqttServerKeepAliveMonitor
|
||||||
{
|
{
|
||||||
@@ -70,7 +70,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
try
|
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.
|
// The connection is already dead or just created so there is no need to check it.
|
||||||
return;
|
return;
|
||||||
@@ -94,14 +94,14 @@ namespace MQTTnet.Server
|
|||||||
// If the client sends 1 sec. the server will allow up to 1.5 seconds.
|
// If the client sends 1 sec. the server will allow up to 1.5 seconds.
|
||||||
var maxDurationWithoutPacket = connection.ConnectPacket.KeepAlivePeriod * 1.5D;
|
var maxDurationWithoutPacket = connection.ConnectPacket.KeepAlivePeriod * 1.5D;
|
||||||
|
|
||||||
var secondsWithoutPackage = (now - connection.LastPacketReceivedTimestamp).TotalSeconds;
|
var secondsWithoutPackage = (now - connection.Statistics.LastPacketReceivedTimestamp).TotalSeconds;
|
||||||
if (secondsWithoutPackage < maxDurationWithoutPacket)
|
if (secondsWithoutPackage < maxDurationWithoutPacket)
|
||||||
{
|
{
|
||||||
// A packet was received before the timeout is affected.
|
// A packet was received before the timeout is affected.
|
||||||
return;
|
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
|
// Execute the disconnection in background so that the keep alive monitor can continue
|
||||||
// with checking other connections.
|
// with checking other connections.
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server.Internal
|
||||||
{
|
{
|
||||||
public static class MqttTopicFilterComparer
|
public static class MqttTopicFilterComparer
|
||||||
{
|
{
|
||||||
@@ -1,35 +1,27 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class MqttApplicationMessageInterceptorContext
|
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>
|
/// <summary>
|
||||||
/// Gets the currently used logger.
|
/// Gets the currently used logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IMqttNetScopedLogger Logger { get; }
|
public IMqttNetScopedLogger Logger { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; }
|
public string ClientId { get; internal set; }
|
||||||
|
|
||||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<object, object> SessionItems { get; }
|
public IDictionary<object, object> SessionItems { get; internal set; }
|
||||||
|
|
||||||
public bool AcceptPublish { get; set; } = true;
|
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
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public class MqttClientMessageQueueInterceptorContext
|
public sealed class MqttClientMessageQueueInterceptorContext
|
||||||
{
|
{
|
||||||
public MqttClientMessageQueueInterceptorContext(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, MqttQualityOfServiceLevel subscriptionQualityOfServiceLevel)
|
public string SenderClientId { get; internal set; }
|
||||||
{
|
|
||||||
SenderClientId = senderClientId;
|
|
||||||
ReceiverClientId = receiverClientId;
|
|
||||||
ApplicationMessage = applicationMessage;
|
|
||||||
SubscriptionQualityOfServiceLevel = subscriptionQualityOfServiceLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string SenderClientId { get; }
|
public string ReceiverClientId { get; internal set; }
|
||||||
|
|
||||||
public string ReceiverClientId { get; }
|
|
||||||
|
|
||||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
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)
|
public MqttClientMessageQueueInterceptorDelegate(Action<MqttClientMessageQueueInterceptorContext> callback)
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
|
|||||||
_callback = context =>
|
_callback = context =>
|
||||||
{
|
{
|
||||||
callback(context);
|
callback(context);
|
||||||
return Task.FromResult(0);
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,21 @@ using System.Security.Cryptography.X509Certificates;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public class MqttConnectionValidatorContext
|
public sealed class MqttConnectionValidatorContext
|
||||||
{
|
{
|
||||||
private readonly MqttConnectPacket _connectPacket;
|
readonly MqttConnectPacket _connectPacket;
|
||||||
private readonly IMqttChannelAdapter _clientAdapter;
|
readonly IMqttChannelAdapter _clientAdapter;
|
||||||
|
|
||||||
public MqttConnectionValidatorContext(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter, IDictionary<object, object> sessionItems)
|
public MqttConnectionValidatorContext(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
|
||||||
{
|
{
|
||||||
_connectPacket = connectPacket;
|
_connectPacket = connectPacket;
|
||||||
_clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
_clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
||||||
SessionItems = sessionItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -39,7 +39,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public byte[] RawPassword => _connectPacket?.Password;
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the will delay interval.
|
/// Gets or sets the will delay interval.
|
||||||
@@ -128,7 +128,7 @@ namespace MQTTnet.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<object, object> SessionItems { get; }
|
public IDictionary<object, object> SessionItems { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is used for MQTTv3 only.
|
/// This is used for MQTTv3 only.
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace MQTTnet.Server
|
using MQTTnet.Server.Internal;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class MqttPendingApplicationMessage
|
public sealed class MqttPendingApplicationMessage
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using MQTTnet.Protocol;
|
|||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public class MqttQueuedApplicationMessage
|
public sealed class MqttQueuedApplicationMessage
|
||||||
{
|
{
|
||||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Implementations;
|
using MQTTnet.Implementations;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Server.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
|
|||||||
_callback = context =>
|
_callback = context =>
|
||||||
{
|
{
|
||||||
callback(context);
|
callback(context);
|
||||||
return Task.FromResult(0);
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,29 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public class MqttServerClientConnectedEventArgs : EventArgs
|
public sealed class MqttServerClientConnectedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public MqttServerClientConnectedEventArgs(string clientId)
|
|
||||||
{
|
|
||||||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </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;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
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)
|
public MqttServerClientConnectedHandlerDelegate(Action<MqttServerClientConnectedEventArgs> handler)
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
|
|||||||
_handler = eventArgs =>
|
_handler = eventArgs =>
|
||||||
{
|
{
|
||||||
handler(eventArgs);
|
handler(eventArgs);
|
||||||
return Task.FromResult(0);
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,23 +2,16 @@ using System;
|
|||||||
|
|
||||||
namespace MQTTnet.Server
|
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>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </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;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
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)
|
public MqttServerClientDisconnectedHandlerDelegate(Action<MqttServerClientDisconnectedEventArgs> handler)
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
|
|||||||
_handler = eventArgs =>
|
_handler = eventArgs =>
|
||||||
{
|
{
|
||||||
handler(eventArgs);
|
handler(eventArgs);
|
||||||
return Task.FromResult(0);
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,24 +2,18 @@
|
|||||||
|
|
||||||
namespace MQTTnet.Server
|
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>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; }
|
public string ClientId { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the topic filter.
|
/// Gets the topic filter.
|
||||||
/// The topic filter can contain topics and wildcards.
|
/// The topic filter can contain topics and wildcards.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttTopicFilter TopicFilter { get; }
|
public MqttTopicFilter TopicFilter { get; internal set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,37 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
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));
|
if (handler == null) throw new ArgumentNullException(nameof(handler));
|
||||||
|
|
||||||
_handler = eventArgs =>
|
_handler = eventArgs =>
|
||||||
{
|
{
|
||||||
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));
|
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,18 @@
|
|||||||
|
|
||||||
namespace MQTTnet.Server
|
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>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; }
|
public string ClientId { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the topic filter.
|
/// Gets or sets the topic filter.
|
||||||
/// The topic filter can contain topics and wildcards.
|
/// The topic filter can contain topics and wildcards.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string TopicFilter { get; }
|
public string TopicFilter { get; internal set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
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)
|
public MqttServerClientUnsubscribedTopicHandlerDelegate(Action<MqttServerClientUnsubscribedTopicEventArgs> handler)
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
|
|||||||
_handler = eventArgs =>
|
_handler = eventArgs =>
|
||||||
{
|
{
|
||||||
handler(eventArgs);
|
handler(eventArgs);
|
||||||
return Task.FromResult(0);
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
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)
|
public MqttServerConnectionValidatorDelegate(Action<MqttConnectionValidatorContext> callback)
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
|
|||||||
_callback = context =>
|
_callback = context =>
|
||||||
{
|
{
|
||||||
callback(context);
|
callback(context);
|
||||||
return Task.FromResult(0);
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ namespace MQTTnet.Server
|
|||||||
_callback = context =>
|
_callback = context =>
|
||||||
{
|
{
|
||||||
callback(context);
|
callback(context);
|
||||||
return Task.FromResult(0);
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using MQTTnet.Server.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
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)
|
public MqttServerStartedHandlerDelegate(Action<EventArgs> handler)
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
|
|||||||
_handler = eventArgs =>
|
_handler = eventArgs =>
|
||||||
{
|
{
|
||||||
handler(eventArgs);
|
handler(eventArgs);
|
||||||
return Task.FromResult(0);
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
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)
|
public MqttServerStoppedHandlerDelegate(Action<EventArgs> handler)
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
|
|||||||
_handler = eventArgs =>
|
_handler = eventArgs =>
|
||||||
{
|
{
|
||||||
handler(eventArgs);
|
handler(eventArgs);
|
||||||
return Task.FromResult(0);
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
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)
|
public MqttServerSubscriptionInterceptorDelegate(Action<MqttSubscriptionInterceptorContext> callback)
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
|
|||||||
_callback = context =>
|
_callback = context =>
|
||||||
{
|
{
|
||||||
callback(context);
|
callback(context);
|
||||||
return Task.FromResult(0);
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,13 @@
|
|||||||
|
|
||||||
namespace MQTTnet.Server
|
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>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; }
|
public string ClientId { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the topic filter.
|
/// Gets or sets the topic filter.
|
||||||
@@ -26,7 +19,7 @@ namespace MQTTnet.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<object, object> SessionItems { get; }
|
public IDictionary<object, object> SessionItems { get; internal set; }
|
||||||
|
|
||||||
public bool AcceptSubscription { get; set; } = true;
|
public bool AcceptSubscription { get; set; } = true;
|
||||||
|
|
||||||
|
|||||||
@@ -2,32 +2,25 @@
|
|||||||
|
|
||||||
namespace MQTTnet.Server
|
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>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; }
|
public string ClientId { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MQTT topic.
|
/// 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.
|
/// 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).
|
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Topic { get; set; }
|
public string Topic { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<object, object> SessionItems { get; }
|
public IDictionary<object, object> SessionItems { get; internal set; }
|
||||||
|
|
||||||
public bool AcceptUnsubscription { get; set; } = true;
|
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; }
|
MqttProtocolVersion ProtocolVersion { get; }
|
||||||
|
|
||||||
|
DateTime ConnectedTimestamp { get; set; }
|
||||||
|
|
||||||
DateTime LastPacketReceivedTimestamp { get; }
|
DateTime LastPacketReceivedTimestamp { get; }
|
||||||
|
|
||||||
|
DateTime LastPacketSentTimestamp { get; set; }
|
||||||
|
|
||||||
DateTime LastNonKeepAlivePacketReceivedTimestamp { get; }
|
DateTime LastNonKeepAlivePacketReceivedTimestamp { get; }
|
||||||
|
|
||||||
long ReceivedApplicationMessagesCount { get; }
|
long ReceivedApplicationMessagesCount { get; }
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ namespace MQTTnet.Server.Status
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
string ClientId { get; }
|
string ClientId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the count of messages which are not yet sent to the client but already queued.
|
||||||
|
/// </summary>
|
||||||
long PendingApplicationMessagesCount { get; }
|
long PendingApplicationMessagesCount { get; }
|
||||||
|
|
||||||
IDictionary<object, object> Items { get; }
|
IDictionary<object, object> Items { get; }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Client.Disconnecting;
|
using MQTTnet.Client.Disconnecting;
|
||||||
|
using MQTTnet.Server.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Server.Status
|
namespace MQTTnet.Server.Status
|
||||||
{
|
{
|
||||||
@@ -24,9 +25,11 @@ namespace MQTTnet.Server.Status
|
|||||||
|
|
||||||
public MqttProtocolVersion ProtocolVersion { get; set; }
|
public MqttProtocolVersion ProtocolVersion { get; set; }
|
||||||
|
|
||||||
public DateTime LastPacketReceivedTimestamp { get; set; }
|
|
||||||
|
|
||||||
public DateTime ConnectedTimestamp { get; set; }
|
public DateTime ConnectedTimestamp { get; set; }
|
||||||
|
|
||||||
|
public DateTime LastPacketReceivedTimestamp { get; set; }
|
||||||
|
|
||||||
|
public DateTime LastPacketSentTimestamp { get; set; }
|
||||||
|
|
||||||
public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; set; }
|
public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; set; }
|
||||||
|
|
||||||
@@ -43,7 +46,7 @@ namespace MQTTnet.Server.Status
|
|||||||
public long BytesSent { get; set; }
|
public long BytesSent { get; set; }
|
||||||
|
|
||||||
public long BytesReceived { get; set; }
|
public long BytesReceived { get; set; }
|
||||||
|
|
||||||
public Task DisconnectAsync()
|
public Task DisconnectAsync()
|
||||||
{
|
{
|
||||||
return _connection.StopAsync(MqttClientDisconnectReason.NormalDisconnection);
|
return _connection.StopAsync(MqttClientDisconnectReason.NormalDisconnection);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Server.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Server.Status
|
namespace MQTTnet.Server.Status
|
||||||
{
|
{
|
||||||
@@ -25,8 +26,11 @@ namespace MQTTnet.Server.Status
|
|||||||
|
|
||||||
public DateTime CreatedTimestamp { get; set; }
|
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 IDictionary<object, object> Items { get; set; }
|
||||||
|
|
||||||
public Task DeleteAsync()
|
public Task DeleteAsync()
|
||||||
{
|
{
|
||||||
return _sessionsManager.DeleteSessionAsync(ClientId);
|
return _sessionsManager.DeleteSessionAsync(ClientId);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using System;
|
using System;
|
||||||
|
using MQTTnet.Server.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
namespace MQTTnet.Tests.MQTTv5
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class Client_Tests
|
public sealed class Client_Tests
|
||||||
{
|
{
|
||||||
public TestContext TestContext { get; set; }
|
public TestContext TestContext { get; set; }
|
||||||
|
|
||||||
@@ -60,6 +60,7 @@ namespace MQTTnet.Tests.MQTTv5
|
|||||||
Assert.AreEqual(2, receivedMessage.UserProperties.Count);
|
Assert.AreEqual(2, receivedMessage.UserProperties.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Connect_With_AssignedClientId()
|
public async Task Connect_With_AssignedClientId()
|
||||||
{
|
{
|
||||||
@@ -116,7 +117,6 @@ namespace MQTTnet.Tests.MQTTv5
|
|||||||
Assert.AreEqual("test123", serverConnectedClientId);
|
Assert.AreEqual("test123", serverConnectedClientId);
|
||||||
Assert.AreEqual("test123", serverDisconnectedClientId);
|
Assert.AreEqual("test123", serverDisconnectedClientId);
|
||||||
Assert.AreEqual("test123", clientAssignedClientId);
|
Assert.AreEqual("test123", clientAssignedClientId);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +165,6 @@ namespace MQTTnet.Tests.MQTTv5
|
|||||||
|
|
||||||
Assert.AreEqual(1, result.Items.Count);
|
Assert.AreEqual(1, result.Items.Count);
|
||||||
Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS1, result.Items[0].ResultCode);
|
Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS1, result.Items[0].ResultCode);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -172,21 +172,25 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
return StartServerAsync(new MqttServerOptionsBuilder());
|
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)
|
if (Server != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Server already started.");
|
throw new InvalidOperationException("Server already started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Server = new TestServerWrapper(_mqttFactory.CreateMqttServer(ServerLogger), TestContext, this);
|
Server = new TestServerWrapper(_mqttFactory.CreateMqttServer(ServerLogger), TestContext, this);
|
||||||
|
return Server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IMqttServer> StartServerAsync(MqttServerOptionsBuilder options)
|
||||||
|
{
|
||||||
|
CreateServer();
|
||||||
|
|
||||||
options.WithDefaultEndpointPort(ServerPort);
|
options.WithDefaultEndpointPort(ServerPort);
|
||||||
options.WithMaxPendingMessagesPerClient(int.MaxValue);
|
options.WithMaxPendingMessagesPerClient(int.MaxValue);
|
||||||
|
|
||||||
await Server.StartAsync(options.Build()).ConfigureAwait(false);
|
await Server.StartAsync(options.Build());
|
||||||
|
|
||||||
return Server;
|
return Server;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ namespace MQTTnet.Tests
|
|||||||
Topic = "Test"
|
Topic = "Test"
|
||||||
}, CancellationToken.None);
|
}, CancellationToken.None);
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(500);
|
||||||
|
|
||||||
// This will simulate a device which closes the connection directly
|
// This will simulate a device which closes the connection directly
|
||||||
// after sending the data so do delay is added between send and dispose!
|
// 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 MQTTnet.Tests.Mockups;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Server.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Tests
|
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
|
namespace MQTTnet.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class Server_Status_Tests
|
public sealed class Server_Status_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
public TestContext TestContext { get; set; }
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Show_Client_And_Session_Statistics()
|
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
|
// At most once will send one packet to the client and the server will reply
|
||||||
// with an additional ACK packet.
|
// with an additional ACK packet.
|
||||||
await c1.PublishAsync("a", string.Empty, MqttQualityOfServiceLevel.AtLeastOnce);
|
await c1.PublishAsync("a", string.Empty, MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
await Task.Delay(50);
|
await Task.Delay(250);
|
||||||
|
|
||||||
var clientStatus = await server.GetClientStatusAsync();
|
var clientStatus = await server.GetClientStatusAsync();
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ namespace MQTTnet.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[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))
|
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||||
{
|
{
|
||||||
@@ -155,7 +155,7 @@ namespace MQTTnet.Tests
|
|||||||
|
|
||||||
await testEnvironment.StartServerAsync();
|
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);
|
var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage);
|
||||||
|
|
||||||
@@ -171,6 +171,38 @@ namespace MQTTnet.Tests
|
|||||||
Assert.AreEqual(0, receivedMessagesCount);
|
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]
|
[TestMethod]
|
||||||
public async Task Will_Message_Send()
|
public async Task Will_Message_Send()
|
||||||
@@ -571,7 +603,12 @@ namespace MQTTnet.Tests
|
|||||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||||
{
|
{
|
||||||
var server = await testEnvironment.StartServerAsync();
|
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 client = await testEnvironment.ConnectClientAsync();
|
||||||
var receivedMessages = new List<MqttApplicationMessage>();
|
var receivedMessages = new List<MqttApplicationMessage>();
|
||||||
@@ -636,11 +673,11 @@ namespace MQTTnet.Tests
|
|||||||
Assert.AreEqual(1, clientConnectedCalled);
|
Assert.AreEqual(1, clientConnectedCalled);
|
||||||
Assert.AreEqual(0, clientDisconnectedCalled);
|
Assert.AreEqual(0, clientDisconnectedCalled);
|
||||||
|
|
||||||
await Task.Delay(500);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
await c1.DisconnectAsync();
|
await c1.DisconnectAsync();
|
||||||
|
|
||||||
await Task.Delay(500);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
Assert.AreEqual(1, clientConnectedCalled);
|
Assert.AreEqual(1, clientConnectedCalled);
|
||||||
Assert.AreEqual(1, clientDisconnectedCalled);
|
Assert.AreEqual(1, clientDisconnectedCalled);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Server.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Tests
|
namespace MQTTnet.Tests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
Console.WriteLine("a = Start QoS 2 benchmark");
|
Console.WriteLine("a = Start QoS 2 benchmark");
|
||||||
Console.WriteLine("b = Start QoS 1 benchmark");
|
Console.WriteLine("b = Start QoS 1 benchmark");
|
||||||
Console.WriteLine("c = Start QoS 0 benchmark");
|
Console.WriteLine("c = Start QoS 0 benchmark");
|
||||||
|
Console.WriteLine("d = Start server with logging");
|
||||||
|
|
||||||
var pressedKey = Console.ReadKey(true);
|
var pressedKey = Console.ReadKey(true);
|
||||||
if (pressedKey.KeyChar == '1')
|
if (pressedKey.KeyChar == '1')
|
||||||
@@ -83,6 +84,10 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
{
|
{
|
||||||
Task.Run(PerformanceTest.RunQoS0Test);
|
Task.Run(PerformanceTest.RunQoS0Test);
|
||||||
}
|
}
|
||||||
|
else if (pressedKey.KeyChar == 'd')
|
||||||
|
{
|
||||||
|
Task.Run(ServerTest.RunEmptyServerWithLogging);
|
||||||
|
}
|
||||||
|
|
||||||
Thread.Sleep(Timeout.Infinite);
|
Thread.Sleep(Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Client.Receiving;
|
using MQTTnet.Client.Receiving;
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Server.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.TestApp.NetCore
|
namespace MQTTnet.TestApp.NetCore
|
||||||
{
|
{
|
||||||
@@ -13,6 +15,19 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
{
|
{
|
||||||
var mqttServer = new MqttFactory().CreateMqttServer();
|
var mqttServer = new MqttFactory().CreateMqttServer();
|
||||||
mqttServer.StartAsync(new MqttServerOptions()).GetAwaiter().GetResult();
|
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.WriteLine("Press any key to exit.");
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
|
|||||||
Reference in New Issue
Block a user