mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 17:00:03 +08:00
Refactor ConnectedClient statistics API
This commit is contained in:
@@ -32,7 +32,8 @@ namespace MQTTnet.AspNetCore
|
||||
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
|
||||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol))
|
||||
{
|
||||
await adapter.AcceptWebSocketAsync(webSocket);
|
||||
var endpoint = $"{context.Connection.RemoteIpAddress}:{context.Connection.RemotePort}";
|
||||
await adapter.RunWebSocketConnectionAsync(webSocket, endpoint);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ namespace MQTTnet.AspNetCore
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task AcceptWebSocketAsync(WebSocket webSocket)
|
||||
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, string endpoint)
|
||||
{
|
||||
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
|
||||
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket), new MqttPacketSerializer(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter)));
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket, endpoint), new MqttPacketSerializer(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter)));
|
||||
|
||||
var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter);
|
||||
ClientAccepted?.Invoke(this, eventArgs);
|
||||
|
||||
@@ -9,8 +9,10 @@ namespace MQTTnet.Adapter
|
||||
{
|
||||
public interface IMqttChannelAdapter : IDisposable
|
||||
{
|
||||
IMqttPacketSerializer PacketSerializer { get; }
|
||||
string Endpoint { get; }
|
||||
|
||||
IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
|
||||
Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace MQTTnet.Adapter
|
||||
_logger = logger.CreateChildLogger(nameof(MqttChannelAdapter));
|
||||
}
|
||||
|
||||
public string Endpoint => _channel.Endpoint;
|
||||
|
||||
public IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace MQTTnet.Channel
|
||||
{
|
||||
public interface IMqttChannel : IDisposable
|
||||
{
|
||||
string Endpoint { get; }
|
||||
|
||||
Task ConnectAsync(CancellationToken cancellationToken);
|
||||
Task DisconnectAsync();
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace MQTTnet.Implementations
|
||||
|
||||
public static Func<MqttClientTcpOptions, IEnumerable<ChainValidationResult>> CustomIgnorableServerCertificateErrorsResolver { get; set; }
|
||||
|
||||
public string Endpoint => _socket?.Information?.RemoteAddress?.ToString(); // TODO: Check if contains also the port.
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_socket == null)
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace MQTTnet.Implementations
|
||||
|
||||
public static Func<X509Certificate, X509Chain, SslPolicyErrors, MqttClientTcpOptions, bool> CustomCertificateValidationCallback { get; set; }
|
||||
|
||||
public string Endpoint => _socket?.RemoteEndPoint?.ToString();
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_socket == null)
|
||||
|
||||
@@ -14,17 +14,20 @@ namespace MQTTnet.Implementations
|
||||
private readonly MqttClientWebSocketOptions _options;
|
||||
|
||||
private WebSocket _webSocket;
|
||||
|
||||
|
||||
public MqttWebSocketChannel(MqttClientWebSocketOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public MqttWebSocketChannel(WebSocket webSocket)
|
||||
public MqttWebSocketChannel(WebSocket webSocket, string endpoint)
|
||||
{
|
||||
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
|
||||
Endpoint = endpoint;
|
||||
}
|
||||
|
||||
public string Endpoint { get; }
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var uri = _options.Uri;
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace MQTTnet.Core.Internal
|
||||
{
|
||||
}
|
||||
|
||||
public string Endpoint { get; }
|
||||
|
||||
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
// TODO: Rename to "RegisteredClient"
|
||||
// TODO: Add IsConnected
|
||||
// TODO: Add interface
|
||||
|
||||
public class ConnectedMqttClient
|
||||
{
|
||||
public string ClientId { get; set; }
|
||||
|
||||
public MqttProtocolVersion? ProtocolVersion { get; set; }
|
||||
|
||||
public TimeSpan LastPacketReceived { get; set; }
|
||||
|
||||
public TimeSpan LastNonKeepAlivePacketReceived { get; set; }
|
||||
|
||||
public int PendingApplicationMessages { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public interface IMqttClientSessionStatus
|
||||
{
|
||||
string ClientId { get; }
|
||||
|
||||
string Endpoint { get; }
|
||||
|
||||
bool IsConnected { get; }
|
||||
|
||||
MqttProtocolVersion? ProtocolVersion { get; }
|
||||
|
||||
TimeSpan LastPacketReceived { get; }
|
||||
|
||||
TimeSpan LastNonKeepAlivePacketReceived { get; }
|
||||
|
||||
int PendingApplicationMessagesCount { get; }
|
||||
|
||||
Task DisconnectAsync();
|
||||
|
||||
Task DeleteSessionAsync();
|
||||
|
||||
Task ClearPendingApplicationMessagesAsync();
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Server
|
||||
|
||||
IMqttServerOptions Options { get; }
|
||||
|
||||
Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync();
|
||||
Task<IList<IMqttClientSessionStatus>> GetClientSessionsStatusAsync();
|
||||
|
||||
Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters);
|
||||
Task UnsubscribeAsync(string clientId, IList<string> topicFilters);
|
||||
|
||||
@@ -4,11 +4,11 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientConnectedEventArgs : EventArgs
|
||||
{
|
||||
public MqttClientConnectedEventArgs(ConnectedMqttClient client)
|
||||
public MqttClientConnectedEventArgs(string clientId)
|
||||
{
|
||||
Client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||
}
|
||||
|
||||
public ConnectedMqttClient Client { get; }
|
||||
public string ClientId { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public MqttClientDisconnectedEventArgs(ConnectedMqttClient client, bool wasCleanDisconnect)
|
||||
public MqttClientDisconnectedEventArgs(string clientId, bool wasCleanDisconnect)
|
||||
{
|
||||
Client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||
WasCleanDisconnect = wasCleanDisconnect;
|
||||
}
|
||||
|
||||
public ConnectedMqttClient Client { get; }
|
||||
public string ClientId { get; }
|
||||
|
||||
public bool WasCleanDisconnect { get; }
|
||||
}
|
||||
|
||||
@@ -12,18 +12,18 @@ namespace MQTTnet.Server
|
||||
private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch();
|
||||
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch();
|
||||
|
||||
private readonly string _clientId;
|
||||
private readonly Action _timeoutCallback;
|
||||
private readonly IMqttNetChildLogger _logger;
|
||||
private readonly string _clientId;
|
||||
private readonly Action _callback;
|
||||
|
||||
private Task _workerTask;
|
||||
|
||||
public MqttClientKeepAliveMonitor(string clientId, Action timeoutCallback, IMqttNetChildLogger logger)
|
||||
public MqttClientKeepAliveMonitor(string clientId, Action callback, IMqttNetChildLogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_clientId = clientId;
|
||||
_timeoutCallback = timeoutCallback;
|
||||
_callback = callback;
|
||||
_logger = logger.CreateChildLogger(nameof(MqttClientKeepAliveMonitor));
|
||||
}
|
||||
|
||||
@@ -38,16 +38,13 @@ namespace MQTTnet.Server
|
||||
return;
|
||||
}
|
||||
|
||||
_workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false), cancellationToken);
|
||||
_workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
public void WaitForCompletion()
|
||||
{
|
||||
if (_workerTask != null)
|
||||
{
|
||||
Task.WaitAll(_workerTask);
|
||||
}
|
||||
}
|
||||
////public void WaitForCompletion()
|
||||
////{
|
||||
//// SpinWait.SpinUntil(() => _workerTask == null || _workerTask.IsCanceled || _workerTask.IsCompleted || _workerTask.IsFaulted);
|
||||
////}
|
||||
|
||||
private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -62,9 +59,8 @@ namespace MQTTnet.Server
|
||||
if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D)
|
||||
{
|
||||
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientId);
|
||||
|
||||
_timeoutCallback?.Invoke();
|
||||
|
||||
_callback();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientPendingMessagesQueue : IDisposable
|
||||
{
|
||||
private readonly ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>();
|
||||
private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent();
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly MqttClientSession _clientSession;
|
||||
private readonly IMqttNetChildLogger _logger;
|
||||
|
||||
private ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>();
|
||||
private Task _workerTask;
|
||||
|
||||
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger)
|
||||
@@ -43,15 +43,7 @@ namespace MQTTnet.Server
|
||||
|
||||
_workerTask = Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
public void WaitForCompletion()
|
||||
{
|
||||
if (_workerTask != null)
|
||||
{
|
||||
Task.WaitAll(_workerTask);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Enqueue(MqttBasePacket packet)
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
@@ -75,6 +67,17 @@ namespace MQTTnet.Server
|
||||
_logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
var newQueue = new ConcurrentQueue<MqttBasePacket>();
|
||||
Interlocked.Exchange(ref _queue, newQueue);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_queueAutoResetEvent?.Dispose();
|
||||
}
|
||||
|
||||
private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
@@ -151,10 +154,5 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_queueAutoResetEvent?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,21 +9,26 @@ using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientSession : IDisposable
|
||||
{
|
||||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||
|
||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||
private readonly MqttClientKeepAliveMonitor _keepAliveMonitor;
|
||||
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue;
|
||||
private readonly MqttClientSubscriptionsManager _subscriptionsManager;
|
||||
private readonly MqttClientSessionsManager _sessionsManager;
|
||||
|
||||
private readonly IMqttNetChildLogger _logger;
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly MqttClientSessionsManager _sessionsManager;
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private MqttApplicationMessage _willMessage;
|
||||
private bool _wasCleanDisconnect;
|
||||
private IMqttChannelAdapter _adapter;
|
||||
|
||||
public MqttClientSession(
|
||||
string clientId,
|
||||
@@ -37,27 +42,28 @@ namespace MQTTnet.Server
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_sessionsManager = sessionsManager;
|
||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
||||
|
||||
|
||||
ClientId = clientId;
|
||||
|
||||
_logger = logger.CreateChildLogger(nameof(MqttClientSession));
|
||||
|
||||
KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeout, _logger);
|
||||
SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server);
|
||||
PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
|
||||
_keepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, () => Stop(MqttClientDisconnectType.NotClean), _logger);
|
||||
_subscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server);
|
||||
_pendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
|
||||
}
|
||||
|
||||
public MqttClientSubscriptionsManager SubscriptionsManager { get; }
|
||||
|
||||
public MqttClientPendingMessagesQueue PendingMessagesQueue { get; }
|
||||
|
||||
public MqttClientKeepAliveMonitor KeepAliveMonitor { get; }
|
||||
|
||||
public string ClientId { get; }
|
||||
|
||||
public MqttProtocolVersion? ProtocolVersion { get; private set; }
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
public void FillStatus(MqttClientSessionStatus status)
|
||||
{
|
||||
status.ClientId = ClientId;
|
||||
status.IsConnected = _adapter != null;
|
||||
status.Endpoint = _adapter?.Endpoint;
|
||||
status.ProtocolVersion = _adapter?.PacketSerializer?.ProtocolVersion;
|
||||
status.PendingApplicationMessagesCount = _pendingMessagesQueue.Count;
|
||||
status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived;
|
||||
status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived;
|
||||
}
|
||||
|
||||
public async Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
|
||||
{
|
||||
@@ -66,34 +72,41 @@ namespace MQTTnet.Server
|
||||
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_adapter = adapter;
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_wasCleanDisconnect = false;
|
||||
_willMessage = connectPacket.WillMessage;
|
||||
|
||||
IsConnected = true;
|
||||
ProtocolVersion = adapter.PacketSerializer.ProtocolVersion;
|
||||
_pendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token);
|
||||
_keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token);
|
||||
|
||||
PendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token);
|
||||
KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token);
|
||||
|
||||
await ReceivePacketsAsync(adapter, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
_keepAliveMonitor.PacketReceived(packet);
|
||||
await ProcessReceivedPacketAsync(adapter, packet, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
_logger.Warning(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId);
|
||||
}
|
||||
|
||||
Stop(MqttClientDisconnectType.NotClean);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ProtocolVersion = null;
|
||||
IsConnected = false;
|
||||
_adapter = null;
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
@@ -106,7 +119,8 @@ namespace MQTTnet.Server
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_cancellationTokenSource == null)
|
||||
var cts = _cancellationTokenSource;
|
||||
if (cts == null || cts.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -114,16 +128,16 @@ namespace MQTTnet.Server
|
||||
_wasCleanDisconnect = type == MqttClientDisconnectType.Clean;
|
||||
|
||||
_cancellationTokenSource?.Cancel(false);
|
||||
PendingMessagesQueue.WaitForCompletion();
|
||||
KeepAliveMonitor.WaitForCompletion();
|
||||
|
||||
var willMessage = _willMessage;
|
||||
_willMessage = null; // clear willmessage so it is send just once
|
||||
|
||||
if (willMessage != null && !_wasCleanDisconnect)
|
||||
if (_willMessage != null && !_wasCleanDisconnect)
|
||||
{
|
||||
_sessionsManager.StartDispatchApplicationMessage(this, willMessage);
|
||||
_sessionsManager.StartDispatchApplicationMessage(this, _willMessage);
|
||||
}
|
||||
|
||||
_willMessage = null;
|
||||
|
||||
////_pendingMessagesQueue.WaitForCompletion();
|
||||
////_keepAliveMonitor.WaitForCompletion();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -131,11 +145,11 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage)
|
||||
public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage).ConfigureAwait(false);
|
||||
var result = _subscriptionsManager.CheckSubscriptions(applicationMessage);
|
||||
if (!result.IsSubscribed)
|
||||
{
|
||||
return;
|
||||
@@ -149,26 +163,27 @@ namespace MQTTnet.Server
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
|
||||
}
|
||||
|
||||
PendingMessagesQueue.Enqueue(publishPacket);
|
||||
_pendingMessagesQueue.Enqueue(publishPacket);
|
||||
}
|
||||
|
||||
public async Task SubscribeAsync(IList<TopicFilter> topicFilters)
|
||||
public Task SubscribeAsync(IList<TopicFilter> topicFilters)
|
||||
{
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
SubscriptionsManager.Subscribe(new MqttSubscribePacket
|
||||
_subscriptionsManager.Subscribe(new MqttSubscribePacket
|
||||
{
|
||||
TopicFilters = topicFilters
|
||||
});
|
||||
|
||||
await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false);
|
||||
EnqueueSubscribedRetainedMessages(topicFilters);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task UnsubscribeAsync(IList<string> topicFilters)
|
||||
{
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
SubscriptionsManager.Unsubscribe(new MqttUnsubscribePacket
|
||||
_subscriptionsManager.Unsubscribe(new MqttUnsubscribePacket
|
||||
{
|
||||
TopicFilters = topicFilters
|
||||
});
|
||||
@@ -176,49 +191,18 @@ namespace MQTTnet.Server
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void ClearPendingApplicationMessages()
|
||||
{
|
||||
_pendingMessagesQueue.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SubscriptionsManager?.Dispose();
|
||||
PendingMessagesQueue?.Dispose();
|
||||
_pendingMessagesQueue?.Dispose();
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
}
|
||||
|
||||
private void StopDueToKeepAliveTimeout()
|
||||
{
|
||||
_logger.Info("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId);
|
||||
Stop(MqttClientDisconnectType.NotClean);
|
||||
}
|
||||
|
||||
private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
|
||||
KeepAliveMonitor.PacketReceived(packet);
|
||||
await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
}
|
||||
|
||||
Stop(MqttClientDisconnectType.NotClean);
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
@@ -276,22 +260,21 @@ namespace MQTTnet.Server
|
||||
|
||||
_logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
|
||||
Stop(MqttClientDisconnectType.NotClean);
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters)
|
||||
private void EnqueueSubscribedRetainedMessages(ICollection<TopicFilter> topicFilters)
|
||||
{
|
||||
var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters);
|
||||
var retainedMessages = _retainedMessagesManager.GetSubscribedMessages(topicFilters);
|
||||
foreach (var applicationMessage in retainedMessages)
|
||||
{
|
||||
await EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
|
||||
EnqueueApplicationMessage(applicationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var subscribeResult = SubscriptionsManager.Subscribe(subscribePacket);
|
||||
var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { subscribeResult.ResponsePacket }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (subscribeResult.CloseConnection)
|
||||
@@ -300,13 +283,13 @@ namespace MQTTnet.Server
|
||||
return;
|
||||
}
|
||||
|
||||
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
|
||||
EnqueueSubscribedRetainedMessages(subscribePacket.TopicFilters);
|
||||
}
|
||||
|
||||
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
||||
private Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var unsubscribeResult = SubscriptionsManager.Unsubscribe(unsubscribePacket);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { unsubscribeResult }, cancellationToken);
|
||||
var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket);
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { unsubscribeResult }, cancellationToken);
|
||||
}
|
||||
|
||||
private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
@@ -335,21 +318,21 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
private Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
|
||||
|
||||
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false);
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
private Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
|
||||
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
|
||||
|
||||
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false);
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken);
|
||||
}
|
||||
|
||||
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientSessionStatus : IMqttClientSessionStatus
|
||||
{
|
||||
private readonly MqttClientSessionsManager _sessionsManager;
|
||||
private readonly MqttClientSession _session;
|
||||
|
||||
public MqttClientSessionStatus(MqttClientSessionsManager sessionsManager, MqttClientSession session)
|
||||
{
|
||||
_sessionsManager = sessionsManager;
|
||||
_session = session;
|
||||
}
|
||||
|
||||
public string ClientId { get; set; }
|
||||
public string Endpoint { get; set; }
|
||||
public bool IsConnected { get; set; }
|
||||
public MqttProtocolVersion? ProtocolVersion { get; set; }
|
||||
public TimeSpan LastPacketReceived { get; set; }
|
||||
public TimeSpan LastNonKeepAlivePacketReceived { get; set; }
|
||||
public int PendingApplicationMessagesCount { get; set; }
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
_session.Stop(MqttClientDisconnectType.NotClean);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task DeleteSessionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_session.Stop(MqttClientDisconnectType.NotClean);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsManager.DeleteSession(ClientId);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task ClearPendingApplicationMessagesAsync()
|
||||
{
|
||||
_session.ClearPendingApplicationMessages();
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
@@ -14,8 +14,8 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientSessionsManager : IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
|
||||
private readonly AsyncLock _sessionsLock = new AsyncLock();
|
||||
private readonly ConcurrentDictionary<string, MqttClientSession> _sessions = new ConcurrentDictionary<string, MqttClientSession>();
|
||||
private readonly AsyncLock _sessionPreparationLock = new AsyncLock();
|
||||
|
||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||
private readonly IMqttServerOptions _options;
|
||||
@@ -38,7 +38,6 @@ namespace MQTTnet.Server
|
||||
{
|
||||
var clientId = string.Empty;
|
||||
var wasCleanDisconnect = false;
|
||||
MqttClientSession clientSession = null;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -69,7 +68,7 @@ namespace MQTTnet.Server
|
||||
}
|
||||
|
||||
var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false);
|
||||
clientSession = result.Session;
|
||||
var clientSession = result.Session;
|
||||
|
||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[]
|
||||
{
|
||||
@@ -80,11 +79,7 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
Server.OnClientConnected(new ConnectedMqttClient
|
||||
{
|
||||
ClientId = clientId,
|
||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
|
||||
});
|
||||
Server.OnClientConnected(clientId);
|
||||
|
||||
wasCleanDisconnect = await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
|
||||
}
|
||||
@@ -109,55 +104,36 @@ namespace MQTTnet.Server
|
||||
|
||||
if (!_options.EnablePersistentSessions)
|
||||
{
|
||||
await DeleteSessionAsync(clientId).ConfigureAwait(false);
|
||||
DeleteSession(clientId);
|
||||
}
|
||||
|
||||
Server.OnClientDisconnected(new ConnectedMqttClient
|
||||
{
|
||||
ClientId = clientId,
|
||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion,
|
||||
PendingApplicationMessages = clientSession?.PendingMessagesQueue.Count ?? 0
|
||||
},
|
||||
wasCleanDisconnect);
|
||||
Server.OnClientDisconnected(clientId, wasCleanDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
public Task StopAsync()
|
||||
{
|
||||
await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
try
|
||||
foreach (var session in _sessions)
|
||||
{
|
||||
foreach (var session in _sessions)
|
||||
{
|
||||
session.Value.Stop(MqttClientDisconnectType.NotClean);
|
||||
}
|
||||
session.Value.Stop(MqttClientDisconnectType.NotClean);
|
||||
}
|
||||
|
||||
_sessions.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsLock.Exit();
|
||||
}
|
||||
_sessions.Clear();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
|
||||
public Task<IList<IMqttClientSessionStatus>> GetClientStatusAsync()
|
||||
{
|
||||
await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
try
|
||||
var result = new List<IMqttClientSessionStatus>();
|
||||
foreach (var session in _sessions)
|
||||
{
|
||||
return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
|
||||
{
|
||||
ClientId = s.Value.ClientId,
|
||||
ProtocolVersion = s.Value.ProtocolVersion,
|
||||
LastPacketReceived = s.Value.KeepAliveMonitor.LastPacketReceived,
|
||||
LastNonKeepAlivePacketReceived = s.Value.KeepAliveMonitor.LastNonKeepAlivePacketReceived,
|
||||
PendingApplicationMessages = s.Value.PendingMessagesQueue.Count
|
||||
}).ToList();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsLock.Exit();
|
||||
var status = new MqttClientSessionStatus(this, session.Value);
|
||||
session.Value.FillStatus(status);
|
||||
|
||||
result.Add(status);
|
||||
}
|
||||
|
||||
return Task.FromResult((IList<IMqttClientSessionStatus>)result);
|
||||
}
|
||||
|
||||
public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||
@@ -165,51 +141,41 @@ namespace MQTTnet.Server
|
||||
Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage));
|
||||
}
|
||||
|
||||
public async Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
|
||||
public Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
|
||||
{
|
||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
try
|
||||
if (!_sessions.TryGetValue(clientId, out var session))
|
||||
{
|
||||
if (!_sessions.TryGetValue(clientId, out var session))
|
||||
{
|
||||
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||
}
|
||||
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||
}
|
||||
|
||||
await session.SubscribeAsync(topicFilters).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsLock.Exit();
|
||||
}
|
||||
return session.SubscribeAsync(topicFilters);
|
||||
}
|
||||
|
||||
public async Task UnsubscribeAsync(string clientId, IList<string> topicFilters)
|
||||
public Task UnsubscribeAsync(string clientId, IList<string> topicFilters)
|
||||
{
|
||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
try
|
||||
if (!_sessions.TryGetValue(clientId, out var session))
|
||||
{
|
||||
if (!_sessions.TryGetValue(clientId, out var session))
|
||||
{
|
||||
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||
}
|
||||
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||
}
|
||||
|
||||
await session.UnsubscribeAsync(topicFilters).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsLock.Exit();
|
||||
}
|
||||
return session.UnsubscribeAsync(topicFilters);
|
||||
}
|
||||
|
||||
public void DeleteSession(string clientId)
|
||||
{
|
||||
_sessions.TryRemove(clientId, out _);
|
||||
_logger.Verbose("Session for client '{0}' deleted.", clientId);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_sessionsLock?.Dispose();
|
||||
_sessionPreparationLock?.Dispose();
|
||||
}
|
||||
|
||||
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
|
||||
@@ -229,23 +195,9 @@ namespace MQTTnet.Server
|
||||
return context.ReturnCode;
|
||||
}
|
||||
|
||||
private async Task DeleteSessionAsync(string clientId)
|
||||
{
|
||||
await _sessionsLock.EnterAsync(CancellationToken.None);
|
||||
try
|
||||
{
|
||||
_sessions.Remove(clientId);
|
||||
_logger.Verbose("Session for client '{0}' deleted.", clientId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsLock.Exit();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<GetOrCreateClientSessionResult> PrepareClientSessionAsync(MqttConnectPacket connectPacket)
|
||||
{
|
||||
await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
await _sessionPreparationLock.EnterAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
|
||||
@@ -253,7 +205,7 @@ namespace MQTTnet.Server
|
||||
{
|
||||
if (connectPacket.CleanSession)
|
||||
{
|
||||
_sessions.Remove(connectPacket.ClientId);
|
||||
_sessions.TryRemove(connectPacket.ClientId, out _);
|
||||
|
||||
clientSession.Stop(MqttClientDisconnectType.Clean);
|
||||
clientSession.Dispose();
|
||||
@@ -282,7 +234,7 @@ namespace MQTTnet.Server
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsLock.Exit();
|
||||
_sessionPreparationLock.Exit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,30 +253,22 @@ namespace MQTTnet.Server
|
||||
return;
|
||||
}
|
||||
|
||||
Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage);
|
||||
|
||||
if (applicationMessage.Retain)
|
||||
{
|
||||
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage);
|
||||
foreach (var clientSession in _sessions.Values)
|
||||
{
|
||||
clientSession.EnqueueApplicationMessage(applicationMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while processing application message");
|
||||
}
|
||||
|
||||
await _sessionsLock.EnterAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
foreach (var clientSession in _sessions.Values)
|
||||
{
|
||||
await clientSession.EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsLock.Exit();
|
||||
}
|
||||
}
|
||||
|
||||
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||
|
||||
@@ -2,17 +2,14 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientSubscriptionsManager : IDisposable
|
||||
public sealed class MqttClientSubscriptionsManager
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, MqttQualityOfServiceLevel> _subscriptions = new ConcurrentDictionary<string, MqttQualityOfServiceLevel>();
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly MqttServer _server;
|
||||
private readonly string _clientId;
|
||||
@@ -81,43 +78,30 @@ namespace MQTTnet.Server
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<CheckSubscriptionsResult> CheckSubscriptionsAsync(MqttApplicationMessage applicationMessage)
|
||||
public CheckSubscriptionsResult CheckSubscriptions(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
var qosLevels = new HashSet<MqttQualityOfServiceLevel>();
|
||||
foreach (var subscription in _subscriptions)
|
||||
{
|
||||
var qosLevels = new HashSet<MqttQualityOfServiceLevel>();
|
||||
foreach (var subscription in _subscriptions)
|
||||
if (!MqttTopicFilterComparer.IsMatch(applicationMessage.Topic, subscription.Key))
|
||||
{
|
||||
if (!MqttTopicFilterComparer.IsMatch(applicationMessage.Topic, subscription.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
qosLevels.Add(subscription.Value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (qosLevels.Count == 0)
|
||||
{
|
||||
return new CheckSubscriptionsResult
|
||||
{
|
||||
IsSubscribed = false
|
||||
};
|
||||
}
|
||||
|
||||
return CreateSubscriptionResult(applicationMessage, qosLevels);
|
||||
qosLevels.Add(subscription.Value);
|
||||
}
|
||||
finally
|
||||
|
||||
if (qosLevels.Count == 0)
|
||||
{
|
||||
_semaphore.Release();
|
||||
return new CheckSubscriptionsResult
|
||||
{
|
||||
IsSubscribed = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_semaphore?.Dispose();
|
||||
return CreateSubscriptionResult(applicationMessage, qosLevels);
|
||||
}
|
||||
|
||||
private static MqttSubscribeReturnCode ConvertToMaximumQoS(MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttRetainedMessagesManager : IDisposable
|
||||
public sealed class MqttRetainedMessagesManager
|
||||
{
|
||||
private readonly Dictionary<string, MqttApplicationMessage> _messages = new Dictionary<string, MqttApplicationMessage>();
|
||||
private readonly AsyncLock _messagesLock = new AsyncLock();
|
||||
private readonly ConcurrentDictionary<string, MqttApplicationMessage> _messages = new ConcurrentDictionary<string, MqttApplicationMessage>();
|
||||
private readonly IMqttNetChildLogger _logger;
|
||||
private readonly IMqttServerOptions _options;
|
||||
|
||||
@@ -29,7 +27,6 @@ namespace MQTTnet.Server
|
||||
return;
|
||||
}
|
||||
|
||||
await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync().ConfigureAwait(false);
|
||||
@@ -44,84 +41,61 @@ namespace MQTTnet.Server
|
||||
{
|
||||
_logger.Error(exception, "Unhandled exception while loading retained messages.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_messagesLock.Exit();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await HandleMessageInternalAsync(clientId, applicationMessage);
|
||||
await HandleMessageInternalAsync(clientId, applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Unhandled exception while handling retained messages.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_messagesLock.Exit();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<MqttApplicationMessage>> GetSubscribedMessagesAsync(ICollection<TopicFilter> topicFilters)
|
||||
public IList<MqttApplicationMessage> GetSubscribedMessages(ICollection<TopicFilter> topicFilters)
|
||||
{
|
||||
var retainedMessages = new List<MqttApplicationMessage>();
|
||||
|
||||
await _messagesLock.EnterAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
try
|
||||
foreach (var retainedMessage in _messages.Values)
|
||||
{
|
||||
foreach (var retainedMessage in _messages.Values)
|
||||
foreach (var topicFilter in topicFilters)
|
||||
{
|
||||
foreach (var topicFilter in topicFilters)
|
||||
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
|
||||
{
|
||||
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
retainedMessages.Add(retainedMessage);
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
retainedMessages.Add(retainedMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_messagesLock.Exit();
|
||||
}
|
||||
|
||||
return retainedMessages;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_messagesLock?.Dispose();
|
||||
}
|
||||
|
||||
private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
var saveIsRequired = false;
|
||||
|
||||
if (applicationMessage.Payload?.Any() == false)
|
||||
if (applicationMessage.Payload?.Length > 0)
|
||||
{
|
||||
saveIsRequired = _messages.Remove(applicationMessage.Topic);
|
||||
saveIsRequired = _messages.TryRemove(applicationMessage.Topic, out _);
|
||||
_logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_messages.ContainsKey(applicationMessage.Topic))
|
||||
if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage))
|
||||
{
|
||||
_messages[applicationMessage.Topic] = applicationMessage;
|
||||
saveIsRequired = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingMessage = _messages[applicationMessage.Topic];
|
||||
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0]))
|
||||
{
|
||||
_messages[applicationMessage.Topic] = applicationMessage;
|
||||
|
||||
@@ -37,9 +37,9 @@ namespace MQTTnet.Server
|
||||
|
||||
public IMqttServerOptions Options { get; private set; }
|
||||
|
||||
public Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
|
||||
public Task<IList<IMqttClientSessionStatus>> GetClientSessionsStatusAsync()
|
||||
{
|
||||
return _clientSessionsManager.GetConnectedClientsAsync();
|
||||
return _clientSessionsManager.GetClientStatusAsync();
|
||||
}
|
||||
|
||||
public Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
|
||||
@@ -120,24 +120,23 @@ namespace MQTTnet.Server
|
||||
finally
|
||||
{
|
||||
_clientSessionsManager?.Dispose();
|
||||
_retainedMessagesManager?.Dispose();
|
||||
|
||||
|
||||
_cancellationTokenSource = null;
|
||||
_retainedMessagesManager = null;
|
||||
_clientSessionsManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnClientConnected(ConnectedMqttClient client)
|
||||
internal void OnClientConnected(string clientId)
|
||||
{
|
||||
_logger.Info("Client '{0}': Connected.", client.ClientId);
|
||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client));
|
||||
_logger.Info("Client '{0}': Connected.", clientId);
|
||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientId));
|
||||
}
|
||||
|
||||
internal void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect)
|
||||
internal void OnClientDisconnected(string clientId, bool wasCleanDisconnect)
|
||||
{
|
||||
_logger.Info("Client '{0}': Disconnected (clean={1}).", client.ClientId, wasCleanDisconnect);
|
||||
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client, wasCleanDisconnect));
|
||||
_logger.Info("Client '{0}': Disconnected (clean={1}).", clientId, wasCleanDisconnect);
|
||||
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientId, wasCleanDisconnect));
|
||||
}
|
||||
|
||||
internal void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter)
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace MQTTnet.Core.Tests
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
||||
};
|
||||
|
||||
var result = sm.CheckSubscriptionsAsync(pp).Result;
|
||||
var result = sm.CheckSubscriptions(pp);
|
||||
Assert.IsTrue(result.IsSubscribed);
|
||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace MQTTnet.Core.Tests
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
||||
};
|
||||
|
||||
var result = sm.CheckSubscriptionsAsync(pp).Result;
|
||||
var result = sm.CheckSubscriptions(pp);
|
||||
Assert.IsTrue(result.IsSubscribed);
|
||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ namespace MQTTnet.Core.Tests
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
||||
};
|
||||
|
||||
var result = sm.CheckSubscriptionsAsync(pp).Result;
|
||||
var result = sm.CheckSubscriptions(pp);
|
||||
Assert.IsTrue(result.IsSubscribed);
|
||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
}
|
||||
@@ -90,7 +90,7 @@ namespace MQTTnet.Core.Tests
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
||||
};
|
||||
|
||||
Assert.IsFalse(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed);
|
||||
Assert.IsFalse(sm.CheckSubscriptions(pp).IsSubscribed);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -109,13 +109,13 @@ namespace MQTTnet.Core.Tests
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
||||
};
|
||||
|
||||
Assert.IsTrue(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed);
|
||||
Assert.IsTrue(sm.CheckSubscriptions(pp).IsSubscribed);
|
||||
|
||||
var up = new MqttUnsubscribePacket();
|
||||
up.TopicFilters.Add("A/B/C");
|
||||
sm.Unsubscribe(up);
|
||||
|
||||
Assert.IsFalse(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed);
|
||||
Assert.IsFalse(sm.CheckSubscriptions(pp).IsSubscribed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace MQTTnet.Core.Tests
|
||||
|
||||
public TestMqttCommunicationAdapter Partner { get; set; }
|
||||
|
||||
public string Endpoint { get; }
|
||||
|
||||
public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer();
|
||||
|
||||
public void Dispose()
|
||||
|
||||
Reference in New Issue
Block a user