Fix keep alive checks in server

This commit is contained in:
Christian Kratky
2017-12-04 13:52:14 +01:00
parent d12d2648b7
commit 644e948489
9 changed files with 98 additions and 39 deletions
+1
View File
@@ -22,6 +22,7 @@
* [Server] Added packet statistics for the connected clients. * [Server] Added packet statistics for the connected clients.
* [Server] Fixed a security issue which sends retained packages to a failed subscription. * [Server] Fixed a security issue which sends retained packages to a failed subscription.
* [Server] Fixed the response (MaximumQoS) of a subscription (Thanks to @redbeans2017). * [Server] Fixed the response (MaximumQoS) of a subscription (Thanks to @redbeans2017).
* [Server] The keep alive timeouts are now checked for every client (Thanks to @RainerMueller82).
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</copyright> <copyright>Copyright Christian Kratky 2016-2017</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
@@ -8,7 +8,7 @@ using MQTTnet.Server;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore
{ {
public class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable
{ {
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted; public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
@@ -347,13 +347,8 @@ namespace MQTTnet.Client
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
await Task.Delay(_options.KeepAlivePeriod, cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return;
}
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false); await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
await Task.Delay(_options.KeepAlivePeriod, cancellationToken).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -10,9 +10,9 @@ using MQTTnet.Protocol;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public sealed class MqttClientPendingMessagesQueue public sealed class MqttClientPendingMessagesQueue : IDisposable
{ {
private readonly BlockingCollection<MqttPublishPacket> _pendingPublishPackets = new BlockingCollection<MqttPublishPacket>(); private readonly BlockingCollection<MqttBasePacket> _queue = new BlockingCollection<MqttBasePacket>();
private readonly IMqttServerOptions _options; private readonly IMqttServerOptions _options;
private readonly MqttClientSession _session; private readonly MqttClientSession _session;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
@@ -33,24 +33,24 @@ namespace MQTTnet.Server
return; return;
} }
Task.Run(async () => await SendPendingPublishPacketsAsync(adapter, cancellationToken), cancellationToken).ConfigureAwait(false); Task.Run(async () => await SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken).ConfigureAwait(false);
} }
public void Enqueue(MqttPublishPacket publishPacket) public void Enqueue(MqttBasePacket packet)
{ {
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); if (packet == null) throw new ArgumentNullException(nameof(packet));
_pendingPublishPackets.Add(publishPacket); _queue.Add(packet);
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _session.ClientId); _logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _session.ClientId);
} }
private async Task SendPendingPublishPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
{ {
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
await SendPendingPublishPacketAsync(adapter, cancellationToken); await SendQueuedPacketAsync(adapter, cancellationToken);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -62,12 +62,12 @@ namespace MQTTnet.Server
} }
} }
private async Task SendPendingPublishPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) private async Task SendQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
{ {
MqttPublishPacket packet = null; MqttBasePacket packet = null;
try try
{ {
packet = _pendingPublishPackets.Take(cancellationToken); packet = _queue.Take(cancellationToken);
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packet).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packet).ConfigureAwait(false);
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _session.ClientId); _logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _session.ClientId);
@@ -90,14 +90,22 @@ namespace MQTTnet.Server
_logger.Error<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed (ClientId: {0}).", _session.ClientId); _logger.Error<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed (ClientId: {0}).", _session.ClientId);
} }
if (packet != null && packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) if (packet is MqttPublishPacket publishPacket)
{ {
packet.Dup = true; if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
_pendingPublishPackets.Add(packet, CancellationToken.None); {
publishPacket.Dup = true;
_queue.Add(packet, CancellationToken.None);
}
} }
await _session.StopAsync(); await _session.StopAsync();
} }
} }
public void Dispose()
{
_queue?.Dispose();
}
} }
} }
@@ -12,7 +12,7 @@ using MQTTnet.Serializer;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public sealed class MqttClientSession public sealed class MqttClientSession : IDisposable
{ {
private readonly Stopwatch _lastPacketReceivedTracker = Stopwatch.StartNew(); private readonly Stopwatch _lastPacketReceivedTracker = Stopwatch.StartNew();
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = Stopwatch.StartNew(); private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = Stopwatch.StartNew();
@@ -57,22 +57,29 @@ namespace MQTTnet.Server
public bool IsConnected => _adapter != null; public bool IsConnected => _adapter != null;
public async Task RunAsync(MqttApplicationMessage willMessage, IMqttChannelAdapter adapter) public async Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
{ {
if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket));
if (adapter == null) throw new ArgumentNullException(nameof(adapter)); if (adapter == null) throw new ArgumentNullException(nameof(adapter));
try try
{ {
_lastPacketReceivedTracker.Restart();
_lastNonKeepAlivePacketReceivedTracker.Restart();
var cancellationTokenSource = new CancellationTokenSource(); var cancellationTokenSource = new CancellationTokenSource();
_willMessage = willMessage; _willMessage = connectPacket.WillMessage;
_adapter = adapter; _adapter = adapter;
_cancellationTokenSource = cancellationTokenSource; _cancellationTokenSource = cancellationTokenSource;
_pendingMessagesQueue.Start(adapter, cancellationTokenSource.Token); _pendingMessagesQueue.Start(adapter, cancellationTokenSource.Token);
_lastPacketReceivedTracker.Restart();
_lastNonKeepAlivePacketReceivedTracker.Restart();
if (connectPacket.KeepAlivePeriod > 0)
{
StartCheckingKeepAliveTimeout(TimeSpan.FromSeconds(connectPacket.KeepAlivePeriod), cancellationTokenSource.Token);
}
await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false); await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -136,6 +143,12 @@ namespace MQTTnet.Server
_pendingMessagesQueue.Enqueue(publishPacket); _pendingMessagesQueue.Enqueue(publishPacket);
} }
public void Dispose()
{
_pendingMessagesQueue?.Dispose();
_cancellationTokenSource?.Dispose();
}
private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
{ {
try try
@@ -143,6 +156,14 @@ namespace MQTTnet.Server
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
_lastPacketReceivedTracker.Restart();
if (!(packet is MqttPingReqPacket))
{
_lastNonKeepAlivePacketReceivedTracker.Restart();
}
await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false); await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false);
} }
} }
@@ -163,13 +184,6 @@ namespace MQTTnet.Server
private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken) private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken)
{ {
_lastPacketReceivedTracker.Restart();
if (!(packet is MqttPingReqPacket))
{
_lastNonKeepAlivePacketReceivedTracker.Restart();
}
if (packet is MqttPublishPacket publishPacket) if (packet is MqttPublishPacket publishPacket)
{ {
return HandleIncomingPublishPacketAsync(adapter, publishPacket, cancellationToken); return HandleIncomingPublishPacketAsync(adapter, publishPacket, cancellationToken);
@@ -292,5 +306,42 @@ namespace MQTTnet.Server
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }; var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier };
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response); return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response);
} }
private void StartCheckingKeepAliveTimeout(TimeSpan keepAlivePeriod, CancellationToken cancellationToken)
{
Task.Run(
async () => await CheckKeepAliveTimeoutAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false)
, cancellationToken);
}
private async Task CheckKeepAliveTimeoutAsync(TimeSpan keepAlivePeriod, CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
// Values described here: [MQTT-3.1.2-24].
if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod.TotalSeconds * 1.5D)
{
_logger.Warning<MqttClientSession>("Client '{0}': Did not receive any packet or keep alive signal.", ClientId);
await StopAsync();
return;
}
await Task.Delay(keepAlivePeriod, cancellationToken);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", ClientId);
}
finally
{
_logger.Trace<MqttClientSession>("Client {0}: Stopped checking keep alive timeout.", ClientId);
}
}
} }
} }
@@ -70,7 +70,7 @@ namespace MQTTnet.Server
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
}); });
await clientSession.Session.RunAsync(connectPacket.WillMessage, clientAdapter).ConfigureAwait(false); await clientSession.Session.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -210,9 +210,11 @@ namespace MQTTnet.Server
if (isSessionPresent) if (isSessionPresent)
{ {
if (connectPacket.CleanSession) if (connectPacket.CleanSession)
{ {
_sessions.Remove(connectPacket.ClientId); _sessions.Remove(connectPacket.ClientId);
await clientSession.StopAsync().ConfigureAwait(false); await clientSession.StopAsync().ConfigureAwait(false);
clientSession.Dispose();
clientSession = null; clientSession = null;
_logger.Trace<MqttClientSessionsManager>("Stopped existing session of client '{0}'.", connectPacket.ClientId); _logger.Trace<MqttClientSessionsManager>("Stopped existing session of client '{0}'.", connectPacket.ClientId);
@@ -71,7 +71,6 @@ namespace MQTTnet.Server
} }
_logger.Info<MqttServer>("Started."); _logger.Info<MqttServer>("Started.");
Started?.Invoke(this, new MqttServerStartedEventArgs()); Started?.Invoke(this, new MqttServerStartedEventArgs());
} }
@@ -27,7 +27,9 @@
<TextBox x:Name="ClientId"></TextBox> <TextBox x:Name="ClientId"></TextBox>
<TextBlock>Clean session:</TextBlock> <TextBlock>Clean session:</TextBlock>
<CheckBox x:Name="CleanSession" IsChecked="True"></CheckBox> <CheckBox x:Name="CleanSession" IsChecked="True"></CheckBox>
<TextBlock>Keep alive interval:</TextBlock>
<TextBox x:Name="KeepAliveInterval" Text="5"></TextBox>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<RadioButton x:Name="UseTcp" IsChecked="True" GroupName="connection">TCP</RadioButton> <RadioButton x:Name="UseTcp" IsChecked="True" GroupName="connection">TCP</RadioButton>
<RadioButton x:Name="UseWs" GroupName="connection">WS</RadioButton> <RadioButton x:Name="UseWs" GroupName="connection">WS</RadioButton>
@@ -103,7 +103,8 @@ namespace MQTTnet.TestApp.UniversalWindows
}; };
options.CleanSession = CleanSession.IsChecked == true; options.CleanSession = CleanSession.IsChecked == true;
options.KeepAlivePeriod = TimeSpan.FromSeconds(double.Parse(KeepAliveInterval.Text));
try try
{ {
if (_mqttClient != null) if (_mqttClient != null)