mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-20 19:58:57 +08:00
Refactor package object model.
This commit is contained in:
@@ -12,6 +12,7 @@ namespace MQTTnet.Server
|
||||
MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; }
|
||||
|
||||
TimeSpan DefaultCommunicationTimeout { get; }
|
||||
TimeSpan KeepAliveMonitorInterval { get; }
|
||||
|
||||
IMqttServerConnectionValidator ConnectionValidator { get; }
|
||||
IMqttServerSubscriptionInterceptor SubscriptionInterceptor { get; }
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace MQTTnet.Server
|
||||
readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource();
|
||||
|
||||
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
|
||||
readonly MqttClientKeepAliveMonitor _keepAliveMonitor;
|
||||
readonly MqttClientSessionsManager _sessionsManager;
|
||||
|
||||
readonly IMqttNetScopedLogger _logger;
|
||||
@@ -36,7 +35,6 @@ namespace MQTTnet.Server
|
||||
readonly DateTime _connectedTimestamp;
|
||||
|
||||
volatile Task _packageReceiverTask;
|
||||
DateTime _lastPacketReceivedTimestamp;
|
||||
DateTime _lastNonKeepAlivePacketReceivedTimestamp;
|
||||
|
||||
long _receivedPacketsCount;
|
||||
@@ -69,17 +67,19 @@ namespace MQTTnet.Server
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
_logger = logger.CreateScopedLogger(nameof(MqttClientConnection));
|
||||
|
||||
_keepAliveMonitor = new MqttClientKeepAliveMonitor(ConnectPacket.ClientId, () => StopAsync(), logger);
|
||||
|
||||
_connectedTimestamp = DateTime.UtcNow;
|
||||
_lastPacketReceivedTimestamp = _connectedTimestamp;
|
||||
_lastNonKeepAlivePacketReceivedTimestamp = _lastPacketReceivedTimestamp;
|
||||
LastPacketReceivedTimestamp = _connectedTimestamp;
|
||||
_lastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp;
|
||||
}
|
||||
|
||||
public MqttConnectPacket ConnectPacket { get; }
|
||||
|
||||
public string ClientId => ConnectPacket.ClientId;
|
||||
|
||||
public bool IsReadingPacket => _channelAdapter.IsReadingPacket;
|
||||
|
||||
public DateTime LastPacketReceivedTimestamp { get; private set; }
|
||||
|
||||
public MqttClientSession Session { get; }
|
||||
|
||||
public Task StopAsync(bool isTakeover = false)
|
||||
@@ -115,7 +115,7 @@ namespace MQTTnet.Server
|
||||
status.SentPacketsCount = Interlocked.Read(ref _sentPacketsCount);
|
||||
|
||||
status.ConnectedTimestamp = _connectedTimestamp;
|
||||
status.LastPacketReceivedTimestamp = _lastPacketReceivedTimestamp;
|
||||
status.LastPacketReceivedTimestamp = LastPacketReceivedTimestamp;
|
||||
status.LastNonKeepAlivePacketReceivedTimestamp = _lastNonKeepAlivePacketReceivedTimestamp;
|
||||
|
||||
status.BytesSent = _channelAdapter.BytesSent;
|
||||
@@ -140,16 +140,10 @@ namespace MQTTnet.Server
|
||||
{
|
||||
_logger.Info("Client '{0}': Session started.", ClientId);
|
||||
|
||||
_channelAdapter.ReadingPacketStartedCallback = OnAdapterReadingPacketStarted;
|
||||
_channelAdapter.ReadingPacketCompletedCallback = OnAdapterReadingPacketCompleted;
|
||||
|
||||
Session.WillMessage = ConnectPacket.WillMessage;
|
||||
|
||||
Task.Run(() => SendPendingPacketsAsync(_cancellationToken.Token), _cancellationToken.Token).Forget(_logger);
|
||||
|
||||
// TODO: Change to single thread in SessionManager. Or use SessionManager and stats from KeepAliveMonitor.
|
||||
_keepAliveMonitor.Start(ConnectPacket.KeepAlivePeriod, _cancellationToken.Token);
|
||||
|
||||
await SendAsync(_channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(_connectionValidatorContext)).ConfigureAwait(false);
|
||||
|
||||
Session.IsCleanSession = false;
|
||||
@@ -164,15 +158,13 @@ namespace MQTTnet.Server
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref _sentPacketsCount);
|
||||
_lastPacketReceivedTimestamp = DateTime.UtcNow;
|
||||
LastPacketReceivedTimestamp = DateTime.UtcNow;
|
||||
|
||||
if (!(packet is MqttPingReqPacket || packet is MqttPingRespPacket))
|
||||
{
|
||||
_lastNonKeepAlivePacketReceivedTimestamp = _lastPacketReceivedTimestamp;
|
||||
_lastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp;
|
||||
}
|
||||
|
||||
_keepAliveMonitor.PacketReceived();
|
||||
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
{
|
||||
await HandleIncomingPublishPacketAsync(publishPacket).ConfigureAwait(false);
|
||||
@@ -252,9 +244,6 @@ namespace MQTTnet.Server
|
||||
|
||||
_packetDispatcher.Reset();
|
||||
|
||||
_channelAdapter.ReadingPacketStartedCallback = null;
|
||||
_channelAdapter.ReadingPacketCompletedCallback = null;
|
||||
|
||||
_packageReceiverTask = null;
|
||||
|
||||
if (_isTakeover)
|
||||
@@ -302,10 +291,10 @@ namespace MQTTnet.Server
|
||||
|
||||
async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket)
|
||||
{
|
||||
// TODO: Let the channel adapter create the packet.
|
||||
var subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket, ConnectPacket).ConfigureAwait(false);
|
||||
var subAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateSubAckPacket(subscribePacket, subscribeResult);
|
||||
|
||||
await SendAsync(subscribeResult.ResponsePacket).ConfigureAwait(false);
|
||||
await SendAsync(subAckPacket).ConfigureAwait(false);
|
||||
|
||||
if (subscribeResult.CloseConnection)
|
||||
{
|
||||
@@ -318,9 +307,10 @@ namespace MQTTnet.Server
|
||||
|
||||
async Task HandleIncomingUnsubscribePacketAsync(MqttUnsubscribePacket unsubscribePacket)
|
||||
{
|
||||
// TODO: Let the channel adapter create the packet.
|
||||
var unsubscribeResult = await Session.SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
|
||||
await SendAsync(unsubscribeResult).ConfigureAwait(false);
|
||||
var reasonCodes = await Session.SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
|
||||
var unsubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateUnsubAckPacket(unsubscribePacket, reasonCodes);
|
||||
|
||||
await SendAsync(unsubAckPacket).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket)
|
||||
@@ -389,7 +379,7 @@ namespace MQTTnet.Server
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
queuedApplicationMessage = await Session.ApplicationMessagesQueue.TakeAsync(cancellationToken).ConfigureAwait(false);
|
||||
queuedApplicationMessage = await Session.ApplicationMessagesQueue.DequeueAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (queuedApplicationMessage == null)
|
||||
{
|
||||
return;
|
||||
@@ -503,15 +493,5 @@ namespace MQTTnet.Server
|
||||
Interlocked.Increment(ref _receivedApplicationMessagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
void OnAdapterReadingPacketCompleted()
|
||||
{
|
||||
_keepAliveMonitor?.Resume();
|
||||
}
|
||||
|
||||
void OnAdapterReadingPacketStarted()
|
||||
{
|
||||
_keepAliveMonitor?.Pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientKeepAliveMonitor
|
||||
{
|
||||
readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch();
|
||||
|
||||
readonly string _clientId;
|
||||
readonly Func<Task> _keepAliveElapsedCallback;
|
||||
readonly IMqttNetScopedLogger _logger;
|
||||
|
||||
bool _isPaused;
|
||||
|
||||
public MqttClientKeepAliveMonitor(string clientId, Func<Task> keepAliveElapsedCallback, IMqttNetLogger logger)
|
||||
{
|
||||
_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||
_keepAliveElapsedCallback = keepAliveElapsedCallback ?? throw new ArgumentNullException(nameof(keepAliveElapsedCallback));
|
||||
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
_logger = logger.CreateScopedLogger(nameof(MqttClientKeepAliveMonitor));
|
||||
}
|
||||
|
||||
public void Start(int keepAlivePeriod, CancellationToken cancellationToken)
|
||||
{
|
||||
if (keepAlivePeriod == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken), cancellationToken).Forget(_logger);
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
_isPaused = true;
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
_isPaused = false;
|
||||
}
|
||||
|
||||
public void PacketReceived()
|
||||
{
|
||||
_lastPacketReceivedTracker.Restart();
|
||||
}
|
||||
|
||||
async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_lastPacketReceivedTracker.Restart();
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Values described here: [MQTT-3.1.2-24].
|
||||
// If the client sends 5 sec. the server will allow up to 7.5 seconds.
|
||||
// If the client sends 1 sec. the server will allow up to 1.5 seconds.
|
||||
if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds >= keepAlivePeriod * 1.5D)
|
||||
{
|
||||
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientId);
|
||||
await _keepAliveElapsedCallback().ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// The server checks the keep alive timeout every 50 % of the overall keep alive timeout
|
||||
// because the server allows 1.5 times the keep alive value. This means that a value of 5 allows
|
||||
// up to 7.5 seconds. With an interval of 2.5 (5 / 2) the 7.5 is also affected. Waiting the whole
|
||||
// keep alive time will hit at 10 instead of 7.5 (but only one time instead of two times).
|
||||
await Task.Delay(TimeSpan.FromSeconds(keepAlivePeriod * 0.5D), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose("Client '{0}': Stopped checking keep alive timeout.", _clientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientSession
|
||||
public sealed class MqttClientSession
|
||||
{
|
||||
readonly IMqttNetScopedLogger _logger;
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientSessionApplicationMessagesQueue : Disposable
|
||||
public sealed class MqttClientSessionApplicationMessagesQueue : IDisposable
|
||||
{
|
||||
private readonly AsyncQueue<MqttQueuedApplicationMessage> _messageQueue = new AsyncQueue<MqttQueuedApplicationMessage>();
|
||||
|
||||
private readonly IMqttServerOptions _options;
|
||||
readonly AsyncQueue<MqttQueuedApplicationMessage> _messageQueue = new AsyncQueue<MqttQueuedApplicationMessage>();
|
||||
|
||||
readonly IMqttServerOptions _options;
|
||||
|
||||
public MqttClientSessionApplicationMessagesQueue(IMqttServerOptions options)
|
||||
{
|
||||
@@ -32,22 +32,6 @@ namespace MQTTnet.Server
|
||||
});
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_messageQueue.Clear();
|
||||
}
|
||||
|
||||
public async Task<MqttQueuedApplicationMessage> TakeAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (!dequeueResult.IsSuccess)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return dequeueResult.Item;
|
||||
}
|
||||
|
||||
public void Enqueue(MqttQueuedApplicationMessage queuedApplicationMessage)
|
||||
{
|
||||
if (queuedApplicationMessage == null) throw new ArgumentNullException(nameof(queuedApplicationMessage));
|
||||
@@ -71,14 +55,25 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public async Task<MqttQueuedApplicationMessage> DequeueAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (disposing)
|
||||
var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (!dequeueResult.IsSuccess)
|
||||
{
|
||||
_messageQueue.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
return dequeueResult.Item;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_messageQueue.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_messageQueue?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using MQTTnet.Server.Status;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -63,6 +64,11 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
public List<MqttClientConnection> GetConnections()
|
||||
{
|
||||
return _connections.Values.ToList();
|
||||
}
|
||||
|
||||
public Task HandleClientConnectionAsync(IMqttChannelAdapter clientAdapter)
|
||||
{
|
||||
if (clientAdapter is null) throw new ArgumentNullException(nameof(clientAdapter));
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using MQTTnet.Packets;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientSubscribeResult
|
||||
public sealed class MqttClientSubscribeResult
|
||||
{
|
||||
public MqttSubAckPacket ResponsePacket { get; set; }
|
||||
public List<MqttSubscribeReturnCode> ReturnCodes { get; } = new List<MqttSubscribeReturnCode>();
|
||||
|
||||
public List<MqttSubscribeReasonCode> ReasonCodes { get; } = new List<MqttSubscribeReasonCode>();
|
||||
|
||||
public bool CloseConnection { get; set; }
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientSubscriptionsManager
|
||||
public sealed class MqttClientSubscriptionsManager
|
||||
{
|
||||
private readonly Dictionary<string, MqttTopicFilter> _subscriptions = new Dictionary<string, MqttTopicFilter>();
|
||||
private readonly MqttClientSession _clientSession;
|
||||
private readonly IMqttServerOptions _serverOptions;
|
||||
private readonly MqttServerEventDispatcher _eventDispatcher;
|
||||
readonly Dictionary<string, MqttTopicFilter> _subscriptions = new Dictionary<string, MqttTopicFilter>();
|
||||
readonly MqttClientSession _clientSession;
|
||||
readonly IMqttServerOptions _serverOptions;
|
||||
readonly MqttServerEventDispatcher _eventDispatcher;
|
||||
|
||||
public MqttClientSubscriptionsManager(MqttClientSession clientSession, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions serverOptions)
|
||||
{
|
||||
@@ -28,15 +28,7 @@ namespace MQTTnet.Server
|
||||
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
|
||||
if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket));
|
||||
|
||||
var result = new MqttClientSubscribeResult
|
||||
{
|
||||
ResponsePacket = new MqttSubAckPacket
|
||||
{
|
||||
PacketIdentifier = subscribePacket.PacketIdentifier
|
||||
},
|
||||
|
||||
CloseConnection = false
|
||||
};
|
||||
var result = new MqttClientSubscribeResult();
|
||||
|
||||
foreach (var originalTopicFilter in subscribePacket.TopicFilters)
|
||||
{
|
||||
@@ -46,13 +38,13 @@ namespace MQTTnet.Server
|
||||
|
||||
if (finalTopicFilter == null || string.IsNullOrEmpty(finalTopicFilter.Topic) || !interceptorContext.AcceptSubscription)
|
||||
{
|
||||
result.ResponsePacket.ReturnCodes.Add(MqttSubscribeReturnCode.Failure);
|
||||
result.ResponsePacket.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError);
|
||||
result.ReturnCodes.Add(MqttSubscribeReturnCode.Failure);
|
||||
result.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ResponsePacket.ReturnCodes.Add(ConvertToSubscribeReturnCode(finalTopicFilter.QualityOfServiceLevel));
|
||||
result.ResponsePacket.ReasonCodes.Add(ConvertToSubscribeReasonCode(finalTopicFilter.QualityOfServiceLevel));
|
||||
result.ReturnCodes.Add(ConvertToSubscribeReturnCode(finalTopicFilter.QualityOfServiceLevel));
|
||||
result.ReasonCodes.Add(ConvertToSubscribeReasonCode(finalTopicFilter.QualityOfServiceLevel));
|
||||
}
|
||||
|
||||
if (interceptorContext.CloseConnection)
|
||||
@@ -98,21 +90,18 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MqttUnsubAckPacket> UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket)
|
||||
public async Task<List<MqttUnsubscribeReasonCode>> UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket)
|
||||
{
|
||||
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
|
||||
|
||||
var unsubAckPacket = new MqttUnsubAckPacket
|
||||
{
|
||||
PacketIdentifier = unsubscribePacket.PacketIdentifier
|
||||
};
|
||||
var reasonCodes = new List<MqttUnsubscribeReasonCode>();
|
||||
|
||||
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
||||
{
|
||||
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
|
||||
if (!interceptorContext.AcceptUnsubscription)
|
||||
{
|
||||
unsubAckPacket.ReasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError);
|
||||
reasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -120,11 +109,11 @@ namespace MQTTnet.Server
|
||||
{
|
||||
if (_subscriptions.Remove(topicFilter))
|
||||
{
|
||||
unsubAckPacket.ReasonCodes.Add(MqttUnsubscribeReasonCode.Success);
|
||||
reasonCodes.Add(MqttUnsubscribeReasonCode.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
unsubAckPacket.ReasonCodes.Add(MqttUnsubscribeReasonCode.NoSubscriptionExisted);
|
||||
reasonCodes.Add(MqttUnsubscribeReasonCode.NoSubscriptionExisted);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +123,7 @@ namespace MQTTnet.Server
|
||||
await _eventDispatcher.SafeNotifyClientUnsubscribedTopicAsync(_clientSession.ClientId, topicFilter).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return unsubAckPacket;
|
||||
return reasonCodes;
|
||||
}
|
||||
|
||||
public async Task UnsubscribeAsync(IEnumerable<string> topicFilters)
|
||||
@@ -184,7 +173,7 @@ namespace MQTTnet.Server
|
||||
return CreateSubscriptionResult(qosLevel, qosLevels);
|
||||
}
|
||||
|
||||
private static MqttSubscribeReturnCode ConvertToSubscribeReturnCode(MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
static MqttSubscribeReturnCode ConvertToSubscribeReturnCode(MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
switch (qualityOfServiceLevel)
|
||||
{
|
||||
@@ -195,7 +184,7 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
private static MqttSubscribeReasonCode ConvertToSubscribeReasonCode(MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
static MqttSubscribeReasonCode ConvertToSubscribeReasonCode(MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
switch (qualityOfServiceLevel)
|
||||
{
|
||||
@@ -206,7 +195,7 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<MqttSubscriptionInterceptorContext> InterceptSubscribeAsync(MqttTopicFilter topicFilter)
|
||||
async Task<MqttSubscriptionInterceptorContext> InterceptSubscribeAsync(MqttTopicFilter topicFilter)
|
||||
{
|
||||
var context = new MqttSubscriptionInterceptorContext(_clientSession.ClientId, topicFilter, _clientSession.Items);
|
||||
if (_serverOptions.SubscriptionInterceptor != null)
|
||||
@@ -217,7 +206,7 @@ namespace MQTTnet.Server
|
||||
return context;
|
||||
}
|
||||
|
||||
private async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribeAsync(string topicFilter)
|
||||
async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribeAsync(string topicFilter)
|
||||
{
|
||||
var context = new MqttUnsubscriptionInterceptorContext(_clientSession.ClientId, topicFilter, _clientSession.Items);
|
||||
if (_serverOptions.UnsubscriptionInterceptor != null)
|
||||
@@ -228,7 +217,7 @@ namespace MQTTnet.Server
|
||||
return context;
|
||||
}
|
||||
|
||||
private static CheckSubscriptionsResult CreateSubscriptionResult(MqttQualityOfServiceLevel qosLevel, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels)
|
||||
static CheckSubscriptionsResult CreateSubscriptionResult(MqttQualityOfServiceLevel qosLevel, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels)
|
||||
{
|
||||
MqttQualityOfServiceLevel effectiveQoS;
|
||||
if (subscribedQoSLevels.Contains(qosLevel))
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttServerKeepAliveMonitor
|
||||
{
|
||||
readonly IMqttServerOptions _options;
|
||||
readonly MqttClientSessionsManager _sessionsManager;
|
||||
readonly IMqttNetScopedLogger _logger;
|
||||
|
||||
public MqttServerKeepAliveMonitor(IMqttServerOptions options, MqttClientSessionsManager sessionsManager, IMqttNetLogger logger)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
|
||||
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
_logger = logger.CreateScopedLogger(nameof(MqttServerKeepAliveMonitor));
|
||||
}
|
||||
|
||||
public void Start(CancellationToken cancellationToken)
|
||||
{
|
||||
// The keep alive monitor spawns a real new thread (LongRunning) because it does not
|
||||
// support async/await. Async etc. is avoided here because the thread will usually check
|
||||
// the connections every few milliseconds and thus the context changes (due to async) are
|
||||
// only consuming resources. Also there is just 1 thread for the entire server which is fine at all!
|
||||
Task.Factory.StartNew(_ => DoWork(cancellationToken), cancellationToken, TaskCreationOptions.LongRunning).Forget(_logger);
|
||||
}
|
||||
|
||||
void DoWork(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("Starting keep alive monitor.");
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
TryMaintainConnections();
|
||||
PlatformAbstractionLayer.Sleep(_options.KeepAliveMonitorInterval);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Unhandled exception while checking keep alive timeouts.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose("Stopped checking keep alive timeout.");
|
||||
}
|
||||
}
|
||||
|
||||
void TryMaintainConnections()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
foreach (var connection in _sessionsManager.GetConnections())
|
||||
{
|
||||
TryMaintainConnection(connection, now);
|
||||
}
|
||||
}
|
||||
|
||||
void TryMaintainConnection(MqttClientConnection connection, DateTime now)
|
||||
{
|
||||
try
|
||||
{
|
||||
//if (connection.IsStopped)
|
||||
//{
|
||||
// // The connection is already dead so there is no need to check it.
|
||||
// return;
|
||||
//}
|
||||
|
||||
if (connection.ConnectPacket.KeepAlivePeriod == 0)
|
||||
{
|
||||
// The keep alive feature is not used by the current connection.
|
||||
return;
|
||||
}
|
||||
|
||||
if (connection.IsReadingPacket)
|
||||
{
|
||||
// The connection is currently reading a (large) packet. So it is obviously
|
||||
// doing something and thus "connected".
|
||||
return;
|
||||
}
|
||||
|
||||
// Values described here: [MQTT-3.1.2-24].
|
||||
// If the client sends 5 sec. the server will allow up to 7.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 secondsWithoutPackage = (now - connection.LastPacketReceivedTimestamp).TotalSeconds;
|
||||
if (secondsWithoutPackage < maxDurationWithoutPacket)
|
||||
{
|
||||
// A packet was received before the timeout is affected.
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", connection.ClientId);
|
||||
|
||||
// Execute the disconnection in background so that the keep alive monitor can continue
|
||||
// with checking other connections.
|
||||
Task.Run(() => connection.StopAsync());
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts.", connection.ClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ namespace MQTTnet.Server
|
||||
|
||||
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15);
|
||||
|
||||
public TimeSpan KeepAliveMonitorInterval { get; set; } = TimeSpan.FromMilliseconds(500);
|
||||
|
||||
public IMqttServerConnectionValidator ConnectionValidator { get; set; }
|
||||
|
||||
public IMqttServerApplicationMessageInterceptor ApplicationMessageInterceptor { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user