Refactor session and connection handling in server. Fix QoS level 2 issues.

This commit is contained in:
Christian Kratky
2019-01-27 11:15:32 +01:00
parent ebf0e91ab8
commit 6a2bded181
72 changed files with 2919 additions and 2461 deletions
@@ -105,7 +105,7 @@ namespace MQTTnet.AspNetCore
return null;
}
public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken)
{
var buffer = PacketFormatterAdapter.Encode(packet).AsMemory();
var output = Connection.Transport.Output;
@@ -110,7 +110,7 @@ namespace MQTTnet.Extensions.ManagedClient
return Task.FromResult(0);
}
public async Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage)
public async Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client.Publishing;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.ManagedClient
@@ -35,5 +38,89 @@ namespace MQTTnet.Extensions.ManagedClient
return managedClient.UnsubscribeAsync(topicFilters);
}
public static async Task PublishAsync(this IManagedMqttClient client, IEnumerable<MqttApplicationMessage> applicationMessages)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await client.PublishAsync(applicationMessage).ConfigureAwait(false);
}
}
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, MqttApplicationMessage applicationMessage)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
return client.PublishAsync(applicationMessage, CancellationToken.None);
}
public static async Task PublishAsync(this IManagedMqttClient client, params MqttApplicationMessage[] applicationMessages)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await client.PublishAsync(applicationMessage, CancellationToken.None).ConfigureAwait(false);
}
}
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, string topic)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.PublishAsync(builder => builder
.WithTopic(topic));
}
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, string topic, string payload)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload));
}
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel));
}
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.WithRetainFlag(retain));
}
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder, CancellationToken cancellationToken)
{
var message = builder(new MqttApplicationMessageBuilder()).Build();
return client.PublishAsync(message, cancellationToken);
}
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder)
{
var message = builder(new MqttApplicationMessageBuilder()).Build();
return client.PublishAsync(message, CancellationToken.None);
}
}
}
@@ -20,7 +20,7 @@ namespace MQTTnet.Adapter
Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken);
Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken);
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
}
+32 -16
View File
@@ -53,11 +53,14 @@ namespace MQTTnet.Adapter
try
{
_logger.Verbose("Connecting [Timeout={0}]", timeout);
await Internal.TaskExtensions
.TimeoutAfterAsync(ct => _channel.ConnectAsync(ct), timeout, cancellationToken)
.ConfigureAwait(false);
if (timeout == TimeSpan.Zero)
{
await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false);
}
else
{
await MqttTaskTimeout.WaitAsync(t => _channel.ConnectAsync(t), timeout, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
@@ -76,11 +79,15 @@ namespace MQTTnet.Adapter
try
{
_logger.Verbose("Disconnecting [Timeout={0}]", timeout);
await Internal.TaskExtensions
.TimeoutAfterAsync(ct => _channel.DisconnectAsync(), timeout, cancellationToken)
.ConfigureAwait(false);
if (timeout == TimeSpan.Zero)
{
await _channel.DisconnectAsync(cancellationToken).ConfigureAwait(false);
}
else
{
await MqttTaskTimeout.WaitAsync(
t => _channel.DisconnectAsync(t), timeout, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
@@ -93,13 +100,23 @@ namespace MQTTnet.Adapter
}
}
public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken)
{
await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var packetData = PacketFormatterAdapter.Encode(packet);
await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false);
if (timeout == TimeSpan.Zero)
{
await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false);
}
else
{
await MqttTaskTimeout.WaitAsync(
t => _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, t), timeout, cancellationToken).ConfigureAwait(false);
}
PacketFormatterAdapter.FreeBuffer();
_logger.Verbose("TX ({0} bytes) >>> {1}", packetData.Count, packet);
@@ -126,14 +143,13 @@ namespace MQTTnet.Adapter
try
{
ReceivedMqttPacket receivedMqttPacket;
if (timeout > TimeSpan.Zero)
if (timeout == TimeSpan.Zero)
{
receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfterAsync(ReceiveAsync, timeout, cancellationToken).ConfigureAwait(false);
receivedMqttPacket = await ReceiveAsync(cancellationToken).ConfigureAwait(false);
}
else
{
receivedMqttPacket = await ReceiveAsync(cancellationToken).ConfigureAwait(false);
receivedMqttPacket = await MqttTaskTimeout.WaitAsync(ReceiveAsync, timeout, cancellationToken).ConfigureAwait(false);
}
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
@@ -1,81 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Client.Publishing;
using MQTTnet.Protocol;
namespace MQTTnet
{
public static class ApplicationMessagePublisherExtensions
{
public static async Task PublishAsync(this IApplicationMessagePublisher publisher, IEnumerable<MqttApplicationMessage> applicationMessages)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await publisher.PublishAsync(applicationMessage).ConfigureAwait(false);
}
}
public static async Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await publisher.PublishAsync(applicationMessage).ConfigureAwait(false);
}
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(builder => builder
.WithTopic(topic));
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload));
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel));
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.WithRetainFlag(retain));
}
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder)
{
var message = builder(new MqttApplicationMessageBuilder()).Build();
return publisher.PublishAsync(message);
}
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ namespace MQTTnet.Channel
string Endpoint { get; }
Task ConnectAsync(CancellationToken cancellationToken);
Task DisconnectAsync();
Task DisconnectAsync(CancellationToken cancellationToken);
Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
@@ -1,6 +1,6 @@
namespace MQTTnet.Client.Connecting
{
public class MqttClientConnectResult
public class MqttClientAuthenticateResult
{
public bool IsSessionPresent { get; set; }
@@ -4,11 +4,11 @@ namespace MQTTnet.Client.Connecting
{
public class MqttClientConnectedEventArgs : EventArgs
{
public MqttClientConnectedEventArgs(MqttClientConnectResult connectResult)
public MqttClientConnectedEventArgs(MqttClientAuthenticateResult authenticateResult)
{
ConnectResult = connectResult ?? throw new ArgumentNullException(nameof(connectResult));
AuthenticateResult = authenticateResult ?? throw new ArgumentNullException(nameof(authenticateResult));
}
public MqttClientConnectResult ConnectResult { get; }
public MqttClientAuthenticateResult AuthenticateResult { get; }
}
}
+5 -4
View File
@@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
@@ -16,10 +17,10 @@ namespace MQTTnet.Client
event EventHandler<MqttClientConnectedEventArgs> Connected;
event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
Task<MqttClientConnectResult> ConnectAsync(IMqttClientOptions options);
Task DisconnectAsync(MqttClientDisconnectOptions options);
Task<MqttClientAuthenticateResult> ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken);
Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken);
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options);
Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options);
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken);
Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken);
}
}
+73 -141
View File
@@ -12,7 +12,6 @@ using MQTTnet.Client.Subscribing;
using MQTTnet.Client.Unsubscribing;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.PacketDispatcher;
using MQTTnet.Packets;
using MQTTnet.Protocol;
@@ -29,10 +28,9 @@ namespace MQTTnet.Client
private readonly IMqttClientAdapterFactory _adapterFactory;
private readonly IMqttNetChildLogger _logger;
private CancellationTokenSource _cancellationTokenSource;
private CancellationTokenSource _backgroundCancellationTokenSource;
private Task _packetReceiverTask;
private Task _keepAlivePacketsSenderTask;
private Task _backgroundWorkerTask;
private IMqttChannelAdapter _adapter;
private bool _cleanDisconnectInitiated;
@@ -47,15 +45,18 @@ namespace MQTTnet.Client
}
public event EventHandler<MqttClientConnectedEventArgs> Connected;
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
public IMqttApplicationMessageHandler ReceivedApplicationMessageHandler { get; set; }
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public bool IsConnected { get; private set; }
public IMqttClientOptions Options { get; private set; }
public async Task<MqttClientConnectResult> ConnectAsync(IMqttClientOptions options)
public async Task<MqttClientAuthenticateResult> ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (options.ChannelOptions == null) throw new ArgumentException("ChannelOptions are not set.");
@@ -69,37 +70,34 @@ namespace MQTTnet.Client
_packetIdentifierProvider.Reset();
_packetDispatcher.Reset();
_cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = _cancellationTokenSource.Token;
_backgroundCancellationTokenSource = new CancellationTokenSource();
var backgroundCancellationToken = _backgroundCancellationTokenSource.Token;
_disconnectGate = 0;
var adapter = _adapterFactory.CreateClientAdapter(options, _logger);
_adapter = adapter;
_logger.Verbose($"Trying to connect with server ({Options.ChannelOptions}).");
await _adapter.ConnectAsync(Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false);
_logger.Verbose($"Trying to connect with server '{options.ChannelOptions}' (Timeout={options.CommunicationTimeout}).");
await _adapter.ConnectAsync(options.CommunicationTimeout, cancellationToken).ConfigureAwait(false);
_logger.Verbose("Connection with server established.");
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(cancellationToken), cancellationToken);
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
var connectResult = await AuthenticateAsync(adapter, options.WillMessage, cancellationToken).ConfigureAwait(false);
_logger.Verbose("MQTT connection with server established.");
var authenticateResult = await AuthenticateAsync(adapter, options.WillMessage, cancellationToken).ConfigureAwait(false);
_sendTracker.Restart();
if (Options.KeepAlivePeriod != TimeSpan.Zero)
{
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(cancellationToken), cancellationToken);
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(backgroundCancellationToken), backgroundCancellationToken);
}
_backgroundWorkerTask = Task.Run(() => TryProcessReceivedPacketsAsync(cancellationToken), cancellationToken);
IsConnected = true;
Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResult));
Connected?.Invoke(this, new MqttClientConnectedEventArgs(authenticateResult));
_logger.Info("Connected.");
return connectResult;
return authenticateResult;
}
catch (Exception exception)
{
@@ -114,16 +112,16 @@ namespace MQTTnet.Client
}
}
public async Task DisconnectAsync(MqttClientDisconnectOptions options)
public async Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken)
{
try
{
_cleanDisconnectInitiated = true;
if (IsConnected && _cancellationTokenSource?.IsCancellationRequested == false)
if (IsConnected)
{
var disconnectPacket = CreateDisconnectPacket(options);
await SendAsync(disconnectPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
var disconnectPacket = _adapter.PacketFormatterAdapter.DataConverter.CreateDisconnectPacket(options);
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
}
}
finally
@@ -135,7 +133,7 @@ namespace MQTTnet.Client
}
}
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options)
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken)
{
if (options == null) throw new ArgumentNullException(nameof(options));
@@ -144,11 +142,11 @@ namespace MQTTnet.Client
var subscribePacket = _adapter.PacketFormatterAdapter.DataConverter.CreateSubscribePacket(options);
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false);
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
return _adapter.PacketFormatterAdapter.DataConverter.CreateClientSubscribeResult(subscribePacket, subAckPacket);
}
public async Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options)
public async Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken)
{
if (options == null) throw new ArgumentNullException(nameof(options));
@@ -157,11 +155,11 @@ namespace MQTTnet.Client
var unsubscribePacket = _adapter.PacketFormatterAdapter.DataConverter.CreateUnsubscribePacket(options);
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false);
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
return _adapter.PacketFormatterAdapter.DataConverter.CreateClientUnsubscribeResult(unsubscribePacket, unsubAckPacket);
}
public async Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage)
public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
@@ -173,26 +171,15 @@ namespace MQTTnet.Client
{
case MqttQualityOfServiceLevel.AtMostOnce:
{
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await SendAsync(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
return new MqttClientPublishResult();
return PublishAtMostOnce(publishPacket, cancellationToken);
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var response = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
var result = new MqttClientPublishResult();
if (response.ReasonCode != null)
{
result.ReasonCode = (MqttClientPublishReasonCode)response.ReasonCode;
}
return result;
return PublishAtLeastOnceAsync(publishPacket, cancellationToken);
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
return await PublishExactlyOnceAsync(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
return PublishExactlyOnceAsync(publishPacket, cancellationToken);
}
default:
{
@@ -203,15 +190,15 @@ namespace MQTTnet.Client
public void Dispose()
{
_cancellationTokenSource?.Cancel(false);
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_backgroundCancellationTokenSource?.Cancel(false);
_backgroundCancellationTokenSource?.Dispose();
_backgroundCancellationTokenSource = null;
_adapter?.Dispose();
_adapter = null;
}
private async Task<MqttClientConnectResult> AuthenticateAsync(IMqttChannelAdapter channelAdapter, MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken)
private async Task<MqttClientAuthenticateResult> AuthenticateAsync(IMqttChannelAdapter channelAdapter, MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken)
{
var connectPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnectPacket(
willApplicationMessage,
@@ -225,6 +212,8 @@ namespace MQTTnet.Client
throw new MqttConnectingFailedException(result.ResultCode);
}
_logger.Verbose("Authenticated MQTT connection with server established.");
return result;
}
@@ -250,12 +239,12 @@ namespace MQTTnet.Client
{
if (_adapter != null)
{
_logger.Verbose("Disconnecting [Timeout={0}]", Options.CommunicationTimeout);
await _adapter.DisconnectAsync(Options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
}
await WaitForTaskAsync(_packetReceiverTask, sender).ConfigureAwait(false);
await WaitForTaskAsync(_keepAlivePacketsSenderTask, sender).ConfigureAwait(false);
await WaitForTaskAsync(_backgroundWorkerTask, sender).ConfigureAwait(false);
_logger.Verbose("Disconnected from adapter.");
}
@@ -279,12 +268,12 @@ namespace MQTTnet.Client
{
try
{
if (_cancellationTokenSource?.IsCancellationRequested == true)
if (_backgroundCancellationTokenSource?.IsCancellationRequested == true)
{
return;
}
_cancellationTokenSource?.Cancel(false);
_backgroundCancellationTokenSource?.Cancel(false);
}
catch (Exception exception)
{
@@ -295,11 +284,14 @@ namespace MQTTnet.Client
private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (cancellationToken.IsCancellationRequested)
{
return Task.FromResult(0);
}
_sendTracker.Restart();
return _adapter.SendPacketAsync(packet, cancellationToken);
return _adapter.SendPacketAsync(packet, Options.CommunicationTimeout, cancellationToken);
}
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket
@@ -318,7 +310,7 @@ namespace MQTTnet.Client
{
try
{
await _adapter.SendPacketAsync(requestPacket, cancellationToken).ConfigureAwait(false);
await _adapter.SendPacketAsync(requestPacket, Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false);
return await packetAwaiter.WaitOneAsync(Options.CommunicationTimeout).ConfigureAwait(false);
}
catch (MqttCommunicationTimedOutException)
@@ -398,12 +390,17 @@ namespace MQTTnet.Client
return;
}
if (packet == null && !DisconnectIsPending())
if (packet == null)
{
await DisconnectInternalAsync(_packetReceiverTask, null).ConfigureAwait(false);
if (!DisconnectIsPending())
{
await DisconnectInternalAsync(_packetReceiverTask, null).ConfigureAwait(false);
}
return;
}
_packetDispatcher.Dispatch(packet);
await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
@@ -446,17 +443,25 @@ namespace MQTTnet.Client
{
await TryProcessReceivedPublishPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPubRelPacket pubRelPacket)
{
await SendAsync(new MqttPubCompPacket
{
PacketIdentifier = pubRelPacket.PacketIdentifier,
ReasonCode = MqttPubCompReasonCode.Success
}, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPingReqPacket)
{
await SendAsync(new MqttPingRespPacket(), cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttDisconnectPacket)
{
await DisconnectAsync(null).ConfigureAwait(false);
await DisconnectAsync(null, cancellationToken).ConfigureAwait(false);
}
else
{
throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
_packetDispatcher.Dispatch(packet);
}
}
catch (Exception exception)
@@ -507,22 +512,15 @@ namespace MQTTnet.Client
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false);
var pubRecPacket = new MqttPubRecPacket
{
PacketIdentifier = publishPacket.PacketIdentifier,
ReasonCode = MqttPubRecReasonCode.Success
};
var pubRelPacket = await SendAndReceiveAsync<MqttPubRelPacket>(pubRecPacket, cancellationToken).ConfigureAwait(false);
// QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery)
await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false);
await SendAsync(new MqttPubCompPacket
{
PacketIdentifier = pubRelPacket.PacketIdentifier,
ReasonCode = MqttPubCompReasonCode.Success
}, cancellationToken).ConfigureAwait(false);
await SendAsync(pubRecPacket, cancellationToken).ConfigureAwait(false);
}
else
{
@@ -535,29 +533,18 @@ namespace MQTTnet.Client
}
}
private async Task TryProcessReceivedPacketsAsync(CancellationToken cancellationToken)
private async Task<MqttClientPublishResult> PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
var packet = _packetDispatcher.Take(cancellationToken);
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
return _adapter.PacketFormatterAdapter.DataConverter.CreatePublishResult(null);
}
if (cancellationToken.IsCancellationRequested)
{
return;
}
await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error(exception, "Error while processing packet.");
}
private async Task<MqttClientPublishResult> PublishAtLeastOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var response = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
return _adapter.PacketFormatterAdapter.DataConverter.CreatePublishResult(response);
}
private async Task<MqttClientPublishResult> PublishExactlyOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
@@ -566,8 +553,6 @@ namespace MQTTnet.Client
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
// TODO: Check response code.
var pubRelPacket = new MqttPubRelPacket
{
PacketIdentifier = publishPacket.PacketIdentifier,
@@ -576,36 +561,9 @@ namespace MQTTnet.Client
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
// TODO: Check response code.
var result = new MqttClientPublishResult();
if (pubRecPacket.ReasonCode != null)
{
result.ReasonCode = (MqttClientPublishReasonCode)pubRecPacket.ReasonCode;
}
return result;
return _adapter.PacketFormatterAdapter.DataConverter.CreatePublishResult(pubRecPacket, pubCompPacket);
}
////private void StartReceivingPackets(CancellationToken cancellationToken)
////{
//// _packetReceiverTask = Task.Factory.StartNew(
//// () => TryReceivePacketsAsync(cancellationToken),
//// cancellationToken,
//// TaskCreationOptions.LongRunning,
//// TaskScheduler.Default).Unwrap();
////}
////private void StartSendingKeepAliveMessages(CancellationToken cancellationToken)
////{
//// _keepAlivePacketsSenderTask = Task.Factory.StartNew(
//// () => TrySendKeepAliveMessagesAsync(cancellationToken),
//// cancellationToken,
//// TaskCreationOptions.LongRunning,
//// TaskScheduler.Default).Unwrap();
////}
private Task HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
{
var applicationMessage = _adapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket);
@@ -647,31 +605,5 @@ namespace MQTTnet.Client
{
return Interlocked.CompareExchange(ref _disconnectGate, 1, 0) != 0;
}
private MqttDisconnectPacket CreateDisconnectPacket(MqttClientDisconnectOptions options)
{
var packet = new MqttDisconnectPacket();
if (_adapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
{
if (options == null)
{
packet.ReasonCode = MqttDisconnectReasonCode.NormalDisconnection;
}
else
{
packet.ReasonCode = (MqttDisconnectReasonCode)options.ReasonCode;
}
}
else
{
if (options != null)
{
throw new MqttProtocolViolationException("Reason codes for disconnect are only supported for MQTTv5.");
}
}
return packet;
}
}
}
@@ -1,5 +1,11 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Publishing;
using MQTTnet.Client.Receiving;
using MQTTnet.Client.Subscribing;
using MQTTnet.Client.Unsubscribing;
@@ -98,5 +104,113 @@ namespace MQTTnet.Client
return client.UnsubscribeAsync(options);
}
public static Task<MqttClientAuthenticateResult> ConnectAsync(this IMqttClient client, IMqttClientOptions options)
{
return client.ConnectAsync(options, CancellationToken.None);
}
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectOptions options)
{
return client.DisconnectAsync(options, CancellationToken.None);
}
public static Task<MqttClientSubscribeResult> SubscribeAsync(this IMqttClient client, MqttClientSubscribeOptions options)
{
return client.SubscribeAsync(options, CancellationToken.None);
}
public static Task<MqttClientUnsubscribeResult> UnsubscribeAsync(this IMqttClient client, MqttClientUnsubscribeOptions options)
{
return client.UnsubscribeAsync(options, CancellationToken.None);
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttClient client, MqttApplicationMessage applicationMessage)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
return client.PublishAsync(applicationMessage, CancellationToken.None);
}
public static async Task PublishAsync(this IMqttClient client, IEnumerable<MqttApplicationMessage> applicationMessages)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await client.PublishAsync(applicationMessage).ConfigureAwait(false);
}
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttClient client, string topic)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.Build());
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttClient client, string topic, IEnumerable<byte> payload)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.Build());
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttClient client, string topic, string payload)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.Build());
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.Build());
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.WithRetainFlag(retain)
.Build());
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttClient client, string topic, string payload, bool retain)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.WithRetainFlag(retain)
.Build());
}
}
}
@@ -31,6 +31,11 @@ namespace MQTTnet.Client.Options
return this;
}
public MqttClientOptionsBuilder WithNoKeepAlive()
{
return WithKeepAlivePeriod(TimeSpan.Zero);
}
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value)
{
_options.KeepAlivePeriod = value;
@@ -1,5 +1,7 @@
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Publishing;
using MQTTnet.Client.Subscribing;
using MQTTnet.Client.Unsubscribing;
using MQTTnet.Packets;
@@ -12,7 +14,7 @@ namespace MQTTnet.Formatter
MqttApplicationMessage CreateApplicationMessage(MqttPublishPacket publishPacket);
MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket);
MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket);
MqttConnectPacket CreateConnectPacket(MqttApplicationMessage willApplicationMessage, IMqttClientOptions options);
@@ -23,5 +25,11 @@ namespace MQTTnet.Formatter
MqttSubscribePacket CreateSubscribePacket(MqttClientSubscribeOptions options);
MqttUnsubscribePacket CreateUnsubscribePacket(MqttClientUnsubscribeOptions options);
MqttDisconnectPacket CreateDisconnectPacket(MqttClientDisconnectOptions options);
MqttClientPublishResult CreatePublishResult(MqttPubAckPacket pubAckPacket);
MqttClientPublishResult CreatePublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket);
}
}
@@ -1,7 +1,9 @@
using System;
using System.Linq;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Publishing;
using MQTTnet.Client.Subscribing;
using MQTTnet.Client.Unsubscribing;
using MQTTnet.Exceptions;
@@ -44,7 +46,7 @@ namespace MQTTnet.Formatter.V3
};
}
public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
public MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
{
if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket));
@@ -91,7 +93,7 @@ namespace MQTTnet.Formatter.V3
throw new MqttProtocolViolationException("Received unexpected return code.");
}
return new MqttClientConnectResult
return new MqttClientAuthenticateResult
{
IsSessionPresent = connAckPacket.IsSessionPresent,
ResultCode = resultCode
@@ -173,5 +175,39 @@ namespace MQTTnet.Formatter.V3
return unsubscribePacket;
}
public MqttDisconnectPacket CreateDisconnectPacket(MqttClientDisconnectOptions options)
{
if (options != null)
{
throw new MqttProtocolViolationException("Reason codes for disconnect are only supported for MQTTv5.");
}
return new MqttDisconnectPacket();
}
public MqttClientPublishResult CreatePublishResult(MqttPubAckPacket pubAckPacket)
{
return new MqttClientPublishResult
{
ReasonCode = MqttClientPublishReasonCode.Success
};
}
public MqttClientPublishResult CreatePublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket)
{
if (pubRecPacket == null || pubCompPacket == null)
{
return new MqttClientPublishResult
{
ReasonCode = MqttClientPublishReasonCode.UnspecifiedError
};
}
return new MqttClientPublishResult
{
ReasonCode = MqttClientPublishReasonCode.Success
};
}
}
}
@@ -2,11 +2,14 @@
using System.Collections.Generic;
using System.Linq;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Publishing;
using MQTTnet.Client.Subscribing;
using MQTTnet.Client.Unsubscribing;
using MQTTnet.Exceptions;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Formatter.V5
{
@@ -55,11 +58,11 @@ namespace MQTTnet.Formatter.V5
};
}
public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
public MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
{
if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket));
return new MqttClientConnectResult
return new MqttClientAuthenticateResult
{
IsSessionPresent = connAckPacket.IsSessionPresent,
ResultCode = (MqttClientConnectResultCode)connAckPacket.ReasonCode.Value
@@ -158,5 +161,65 @@ namespace MQTTnet.Formatter.V5
return packet;
}
public MqttDisconnectPacket CreateDisconnectPacket(MqttClientDisconnectOptions options)
{
var packet = new MqttDisconnectPacket();
if (options == null)
{
packet.ReasonCode = MqttDisconnectReasonCode.NormalDisconnection;
}
else
{
packet.ReasonCode = (MqttDisconnectReasonCode)options.ReasonCode;
}
return packet;
}
public MqttClientPublishResult CreatePublishResult(MqttPubAckPacket pubAckPacket)
{
var result = new MqttClientPublishResult();
if (pubAckPacket?.ReasonCode != null)
{
result.ReasonCode = (MqttClientPublishReasonCode)pubAckPacket.ReasonCode;
}
return result;
}
public MqttClientPublishResult CreatePublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket)
{
if (pubRecPacket == null || pubCompPacket == null)
{
return new MqttClientPublishResult
{
ReasonCode = MqttClientPublishReasonCode.UnspecifiedError
};
}
if (pubCompPacket.ReasonCode == MqttPubCompReasonCode.PacketIdentifierNotFound)
{
return new MqttClientPublishResult
{
ReasonCode = MqttClientPublishReasonCode.UnspecifiedError
};
}
var result = new MqttClientPublishResult
{
ReasonCode = MqttClientPublishReasonCode.Success
};
if (pubRecPacket.ReasonCode.HasValue)
{
// Both enums share the same values.
result.ReasonCode = (MqttClientPublishReasonCode)pubRecPacket.ReasonCode.Value;
}
return result;
}
}
}
@@ -1,10 +1,11 @@
using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client.Publishing;
namespace MQTTnet
{
public interface IApplicationMessagePublisher
{
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage);
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken);
}
}
@@ -81,7 +81,7 @@ namespace MQTTnet.Implementations
CreateStreams();
}
public Task DisconnectAsync()
public Task DisconnectAsync(CancellationToken cancellationToken)
{
Dispose();
return Task.FromResult(0);
@@ -72,7 +72,7 @@ namespace MQTTnet.Implementations
CreateStream(sslStream);
}
public Task DisconnectAsync()
public Task DisconnectAsync(CancellationToken cancellationToken)
{
Dispose();
return Task.FromResult(0);
@@ -81,8 +81,13 @@ namespace MQTTnet.Implementations
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
// Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(() => _socket.Dispose()))
using (cancellationToken.Register(Dispose))
{
if (cancellationToken.IsCancellationRequested)
{
return 0;
}
return await _stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
}
}
@@ -90,8 +95,13 @@ namespace MQTTnet.Implementations
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
// Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(() => _socket.Dispose()))
using (cancellationToken.Register(Dispose))
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
await _stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
//await _stream.FlushAsync(cancellationToken);
}
@@ -99,16 +109,21 @@ namespace MQTTnet.Implementations
public void Dispose()
{
Cleanup(ref _stream, s => s.Dispose());
Cleanup(ref _socket, s =>
{
//if (s.Connected)
//{
// s.Shutdown(SocketShutdown.Both);
//}
_socket = null;
s.Dispose();
});
// When the stream is disposed it will also close the socket and this will also dispose it.
// So there is no need to dispose the socket again.
// https://stackoverflow.com/questions/3601521/should-i-manually-dispose-the-socket-after-closing-it
try
{
_stream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
}
private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
@@ -3,7 +3,6 @@ using System;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
@@ -85,7 +85,7 @@ namespace MQTTnet.Implementations
_webSocket = clientWebSocket;
}
public async Task DisconnectAsync()
public async Task DisconnectAsync(CancellationToken cancellationToken)
{
if (_webSocket == null)
{
@@ -94,7 +94,7 @@ namespace MQTTnet.Implementations
if (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.Connecting)
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
}
Dispose();
@@ -5,9 +5,9 @@ using MQTTnet.Exceptions;
namespace MQTTnet.Internal
{
public static class TaskExtensions
public static class MqttTaskTimeout
{
public static async Task TimeoutAfterAsync(Func<CancellationToken, Task> action, TimeSpan timeout, CancellationToken cancellationToken)
public static async Task WaitAsync(Func<CancellationToken, Task> action, TimeSpan timeout, CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException(nameof(action));
@@ -31,7 +31,7 @@ namespace MQTTnet.Internal
}
}
public static async Task<TResult> TimeoutAfterAsync<TResult>(Func<CancellationToken, Task<TResult>> action, TimeSpan timeout, CancellationToken cancellationToken)
public static async Task<TResult> WaitAsync<TResult>(Func<CancellationToken, Task<TResult>> action, TimeSpan timeout, CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException(nameof(action));
+1 -1
View File
@@ -21,7 +21,7 @@ namespace MQTTnet.Internal
return Task.FromResult(0);
}
public Task DisconnectAsync()
public Task DisconnectAsync(CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
@@ -38,12 +39,24 @@ namespace MQTTnet
return this;
}
_payload = payload.ToArray();
_payload = payload as byte[];
if (_payload == null)
{
_payload = payload.ToArray();
}
return this;
}
public MqttApplicationMessageBuilder WithPayload(Stream payload)
{
if (payload == null)
{
_payload = null;
return this;
}
return WithPayload(payload, payload.Length - payload.Position);
}
@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Exceptions;
using MQTTnet.Packets;
namespace MQTTnet.PacketDispatcher
@@ -21,7 +22,7 @@ namespace MQTTnet.PacketDispatcher
{
using (var timeoutToken = new CancellationTokenSource(timeout))
{
timeoutToken.Token.Register(() => _taskCompletionSource.TrySetCanceled());
timeoutToken.Token.Register(() => _taskCompletionSource.TrySetException(new MqttCommunicationTimedOutException()));
var packet = await _taskCompletionSource.Task.ConfigureAwait(false);
return (TPacket)packet;
@@ -1,6 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using MQTTnet.Exceptions;
using MQTTnet.Packets;
namespace MQTTnet.PacketDispatcher
@@ -9,8 +9,6 @@ namespace MQTTnet.PacketDispatcher
{
private readonly ConcurrentDictionary<Tuple<ushort, Type>, IMqttPacketAwaiter> _packetAwaiters = new ConcurrentDictionary<Tuple<ushort, Type>, IMqttPacketAwaiter>();
private BlockingCollection<MqttBasePacket> _inboundPackagesQueue = new BlockingCollection<MqttBasePacket>();
public void Dispatch(Exception exception)
{
foreach (var awaiter in _packetAwaiters)
@@ -40,34 +38,14 @@ namespace MQTTnet.PacketDispatcher
return;
}
lock (_inboundPackagesQueue)
{
_inboundPackagesQueue.Add(packet);
}
}
public MqttBasePacket Take(CancellationToken cancellationToken)
{
BlockingCollection<MqttBasePacket> inboundPackagesQueue;
lock (_inboundPackagesQueue)
{
inboundPackagesQueue = _inboundPackagesQueue;
}
return inboundPackagesQueue.Take(cancellationToken);
throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
}
public void Reset()
{
foreach (var awaiter in _packetAwaiters)
{
awaiter.Value.Cancel();
}
lock (_inboundPackagesQueue)
{
_inboundPackagesQueue?.Dispose();
_inboundPackagesQueue = new BlockingCollection<MqttBasePacket>();
awaiter.Value.Cancel();
}
_packetAwaiters.Clear();
+3 -4
View File
@@ -1,12 +1,11 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttClientSession : IDisposable
public interface IMqttClientSession
{
string ClientId { get; }
Task StopAsync(MqttClientDisconnectType disconnectType);
Task StopAsync();
}
}
@@ -1,33 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Formatter;
namespace MQTTnet.Server
{
public interface IMqttClientSessionStatus
{
string ClientId { get; }
string Endpoint { get; }
bool IsConnected { get; }
MqttProtocolVersion? ProtocolVersion { get; }
TimeSpan LastPacketReceived { get; }
TimeSpan LastNonKeepAlivePacketReceived { get; }
long PendingApplicationMessagesCount { get; }
long ReceivedApplicationMessagesCount { get; }
long SentApplicationMessagesCount { get; }
Task DisconnectAsync();
Task DeleteSessionAsync();
Task ClearPendingApplicationMessagesAsync();
}
}
+6 -4
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Server.Status;
namespace MQTTnet.Server
{
@@ -16,13 +17,14 @@ namespace MQTTnet.Server
IMqttServerOptions Options { get; }
Task<IList<IMqttClientSessionStatus>> GetClientSessionsStatusAsync();
Task<IList<IMqttClientStatus>> GetClientStatusAsync();
Task<IList<IMqttSessionStatus>> GetSessionStatusAsync();
IList<MqttApplicationMessage> GetRetainedMessages();
Task<IList<MqttApplicationMessage>> GetRetainedMessagesAsync();
Task ClearRetainedMessagesAsync();
Task SubscribeAsync(string clientId, IEnumerable<TopicFilter> topicFilters);
Task UnsubscribeAsync(string clientId, IEnumerable<string> topicFilters);
Task SubscribeAsync(string clientId, ICollection<TopicFilter> topicFilters);
Task UnsubscribeAsync(string clientId, ICollection<string> topicFilters);
Task StartAsync(IMqttServerOptions options);
Task StopAsync();
@@ -4,6 +4,6 @@ namespace MQTTnet.Server
{
public interface IMqttServerConnectionValidator
{
Task ValidateConnection(MqttConnectionValidatorContext context);
Task ValidateConnectionAsync(MqttConnectionValidatorContext context);
}
}
@@ -0,0 +1,568 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.PacketDispatcher;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server.Status;
namespace MQTTnet.Server
{
public class MqttClientSession
{
private readonly DateTime _createdTimestamp = DateTime.UtcNow;
public MqttClientSession(string clientId, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions serverOptions)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, eventDispatcher, serverOptions);
ApplicationMessagesQueue = new MqttClientSessionApplicationMessagesQueue(serverOptions);
}
public string ClientId { get; }
public bool IsCleanSession { get; set; } = true;
public MqttApplicationMessage WillMessage { get; set; }
public MqttClientSubscriptionsManager SubscriptionsManager { get; }
public MqttClientSessionApplicationMessagesQueue ApplicationMessagesQueue { get; }
public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage, string senderClientId, bool isRetainedApplicationMessage)
{
var checkSubscriptionsResult = SubscriptionsManager.CheckSubscriptions(applicationMessage.Topic, applicationMessage.QualityOfServiceLevel);
if (!checkSubscriptionsResult.IsSubscribed)
{
return;
}
ApplicationMessagesQueue.Enqueue(applicationMessage, senderClientId, checkSubscriptionsResult.QualityOfServiceLevel, isRetainedApplicationMessage);
}
public async Task SubscribeAsync(ICollection<TopicFilter> topicFilters, MqttRetainedMessagesManager retainedMessagesManager)
{
await SubscriptionsManager.SubscribeAsync(topicFilters).ConfigureAwait(false);
var matchingRetainedMessages = await retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false);
foreach (var matchingRetainedMessage in matchingRetainedMessages)
{
EnqueueApplicationMessage(matchingRetainedMessage, null, true);
}
}
public Task UnsubscribeAsync(IEnumerable<string> topicFilters)
{
return SubscriptionsManager.UnsubscribeAsync(topicFilters);
}
public void FillStatus(MqttSessionStatus status)
{
status.ClientId = ClientId;
status.CreatedTimestamp = _createdTimestamp;
status.PendingApplicationMessagesCount = ApplicationMessagesQueue.Count;
}
}
public class MqttClientConnection : IMqttClientSession, IDisposable
{
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
private readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource();
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
private readonly MqttClientKeepAliveMonitor _keepAliveMonitor;
private readonly MqttClientSessionsManager _sessionsManager;
private readonly IMqttNetChildLogger _logger;
private readonly IMqttServerOptions _serverOptions;
private Task<MqttClientDisconnectType> _packageReceiverTask;
private readonly IMqttChannelAdapter _channelAdapter;
private readonly IMqttDataConverter _dataConverter;
private readonly string _endpoint;
private readonly MqttConnectPacket _connectPacket;
private DateTime _lastPacketReceivedTimestamp;
private long _receivedPacketsCount;
private long _sentPacketsCount;
private long _receivedApplicationMessagesCount;
private long _sentApplicationMessagesCount;
public MqttClientConnection(
MqttConnectPacket connectPacket,
IMqttChannelAdapter channelAdapter,
MqttClientSession session,
IMqttServerOptions serverOptions,
MqttClientSessionsManager sessionsManager,
MqttRetainedMessagesManager retainedMessagesManager,
IMqttNetChildLogger logger)
{
Session = session ?? throw new ArgumentNullException(nameof(session));
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
_dataConverter = _channelAdapter.PacketFormatterAdapter.DataConverter;
_endpoint = _channelAdapter.Endpoint;
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.CreateChildLogger(nameof(MqttClientConnection));
_keepAliveMonitor = new MqttClientKeepAliveMonitor(this, _logger);
_lastPacketReceivedTimestamp = DateTime.UtcNow;
}
public string ClientId => _connectPacket.ClientId;
public MqttClientSession Session { get; }
public async Task StopAsync()
{
StopInternal();
var task = _packageReceiverTask;
if (task != null && !task.IsCompleted)
{
await task.ConfigureAwait(false);
}
}
public void FillStatus(MqttClientStatus status)
{
status.ClientId = ClientId;
status.Endpoint = _endpoint;
status.ProtocolVersion = _channelAdapter.PacketFormatterAdapter.ProtocolVersion.Value;
status.ReceivedApplicationMessagesCount = Interlocked.Read(ref _receivedApplicationMessagesCount);
status.SentApplicationMessagesCount = Interlocked.Read(ref _sentApplicationMessagesCount);
status.ReceivedPacketsCount = Interlocked.Read(ref _receivedPacketsCount);
status.SentPacketsCount = Interlocked.Read(ref _sentPacketsCount);
status.LastPacketReceivedTimestamp = _lastPacketReceivedTimestamp;
//status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived;
}
//public void ClearPendingApplicationMessages()
//{
// Session.ApplicationMessagesQueue.Clear();
// //_applicationMessagesQueue.Clear();
//}
public void Dispose()
{
_cancellationToken.Dispose();
}
public Task<MqttClientDisconnectType> RunAsync()
{
_packageReceiverTask = RunInternalAsync();
return _packageReceiverTask;
}
public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage, string senderClientId, bool isRetainedApplicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
Session.EnqueueApplicationMessage(applicationMessage, senderClientId, isRetainedApplicationMessage);
_logger.Verbose("Enqueued application message (ClientId: {0}).", ClientId);
}
private async Task<MqttClientDisconnectType> RunInternalAsync()
{
var disconnectType = MqttClientDisconnectType.NotClean;
try
{
_logger.Info("Client '{0}': Session started.", ClientId);
//_eventDispatcher.OnClientConnected(ClientId);
_channelAdapter.ReadingPacketStarted += OnAdapterReadingPacketStarted;
_channelAdapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted;
Session.WillMessage = _connectPacket.WillMessage;
Task.Run(() => SendPendingPacketsAsync(), _cancellationToken.Token);
// TODO: Change to single thread in SessionManager. Or use SessionManager and stats from KeepAliveMonitor.
_keepAliveMonitor.Start(_connectPacket.KeepAlivePeriod, _cancellationToken.Token);
await SendAsync(
new MqttConnAckPacket
{
ReturnCode = MqttConnectReturnCode.ConnectionAccepted,
ReasonCode = MqttConnectReasonCode.Success,
IsSessionPresent = Session.IsCleanSession
}).ConfigureAwait(false);
Session.IsCleanSession = false;
while (!_cancellationToken.IsCancellationRequested)
{
var packet = await _channelAdapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationToken.Token).ConfigureAwait(false);
if (packet == null)
{
// The client has closed the connection gracefully.
break;
}
Interlocked.Increment(ref _sentPacketsCount);
_lastPacketReceivedTimestamp = DateTime.UtcNow;
_keepAliveMonitor.PacketReceived();
if (packet is MqttPublishPacket publishPacket)
{
await HandleIncomingPublishPacketAsync(publishPacket).ConfigureAwait(false);
continue;
}
if (packet is MqttPubRelPacket pubRelPacket)
{
var pubCompPacket = new MqttPubCompPacket
{
PacketIdentifier = pubRelPacket.PacketIdentifier,
ReasonCode = MqttPubCompReasonCode.Success
};
await SendAsync(pubCompPacket).ConfigureAwait(false);
continue;
}
if (packet is MqttSubscribePacket subscribePacket)
{
await HandleIncomingSubscribePacketAsync(subscribePacket).ConfigureAwait(false);
continue;
}
if (packet is MqttUnsubscribePacket unsubscribePacket)
{
await HandleIncomingUnsubscribePacketAsync(unsubscribePacket).ConfigureAwait(false);
continue;
}
if (packet is MqttPingReqPacket)
{
await SendAsync(new MqttPingRespPacket()).ConfigureAwait(false);
continue;
}
if (packet is MqttDisconnectPacket)
{
Session.WillMessage = null;
disconnectType = MqttClientDisconnectType.Clean;
StopInternal();
break;
}
_packetDispatcher.Dispatch(packet);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
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);
}
StopInternal();
}
finally
{
if (Session.WillMessage != null)
{
_sessionsManager.DispatchApplicationMessage(Session.WillMessage, this);
Session.WillMessage = null;
}
_packetDispatcher.Reset();
_channelAdapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted;
_channelAdapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted;
_logger.Info("Client '{0}': Session stopped.", ClientId);
//_eventDispatcher.OnClientDisconnected(ClientId);
_packageReceiverTask = null;
}
return disconnectType;
}
private void StopInternal()
{
_cancellationToken.Cancel(false);
}
private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters)
{
var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false);
foreach (var applicationMessage in retainedMessages)
{
EnqueueApplicationMessage(applicationMessage, ClientId, true);
}
}
private async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket)
{
// TODO: Let the channel adapter create the packet.
var subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false);
await SendAsync(subscribeResult.ResponsePacket).ConfigureAwait(false);
if (subscribeResult.CloseConnection)
{
StopInternal();
return;
}
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
}
private 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);
}
private Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket)
{
Interlocked.Increment(ref _sentApplicationMessagesCount);
switch (publishPacket.QualityOfServiceLevel)
{
case MqttQualityOfServiceLevel.AtMostOnce:
{
return HandleIncomingPublishPacketWithQoS0Async(publishPacket);
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
return HandleIncomingPublishPacketWithQoS1Async(publishPacket);
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
return HandleIncomingPublishPacketWithQoS2Async(publishPacket);
}
default:
{
throw new MqttCommunicationException("Received a not supported QoS level.");
}
}
}
private Task HandleIncomingPublishPacketWithQoS0Async(MqttPublishPacket publishPacket)
{
var applicationMessage = _dataConverter.CreateApplicationMessage(publishPacket);
_sessionsManager.DispatchApplicationMessage(applicationMessage, this);
return Task.FromResult(0);
}
private Task HandleIncomingPublishPacketWithQoS1Async(MqttPublishPacket publishPacket)
{
var applicationMessage = _dataConverter.CreateApplicationMessage(publishPacket);
_sessionsManager.DispatchApplicationMessage(applicationMessage, this);
// TODO: Create ACK packet.
var response = new MqttPubAckPacket
{
PacketIdentifier = publishPacket.PacketIdentifier,
ReasonCode = MqttPubAckReasonCode.Success
};
return SendAsync(response);
}
private async Task HandleIncomingPublishPacketWithQoS2Async(MqttPublishPacket publishPacket)
{
var applicationMessage = _dataConverter.CreateApplicationMessage(publishPacket);
_sessionsManager.DispatchApplicationMessage(applicationMessage, this);
var pubRecPacket = new MqttPubRecPacket
{
PacketIdentifier = publishPacket.PacketIdentifier,
ReasonCode = MqttPubRecReasonCode.Success
};
await SendAsync(pubRecPacket).ConfigureAwait(false);
////Task.Run(async () =>
////{
//// using (var pubRelPacketAwaiter = _packetDispatcher.AddPacketAwaiter<MqttPubRelPacket>(publishPacket.PacketIdentifier))
//// {
//// await pubRelPacketAwaiter.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
//// var pubCompPacket = new MqttPubCompPacket
//// {
//// PacketIdentifier = publishPacket.PacketIdentifier,
//// ReasonCode = MqttPubCompReasonCode.Success
//// };
//// await SendAsync(pubCompPacket).ConfigureAwait(false);
//// }
////});
await Task.FromResult(0);
}
private async Task SendPendingPacketsAsync()
{
MqttPendingApplicationMessage enqueuedApplicationMessage = null;
MqttPublishPacket publishPacket = null;
try
{
while (!_cancellationToken.IsCancellationRequested)
{
enqueuedApplicationMessage = await Session.ApplicationMessagesQueue.TakeAsync(_cancellationToken.Token).ConfigureAwait(false);
if (enqueuedApplicationMessage == null)
{
return;
}
if (_cancellationToken.IsCancellationRequested)
{
return;
}
publishPacket = _dataConverter.CreatePublishPacket(enqueuedApplicationMessage.ApplicationMessage);
publishPacket.QualityOfServiceLevel = enqueuedApplicationMessage.QualityOfServiceLevel;
// Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9].
publishPacket.Retain = enqueuedApplicationMessage.IsRetainedMessage;
if (publishPacket.QualityOfServiceLevel > 0)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
}
if (_serverOptions.ClientMessageQueueInterceptor != null)
{
var context = new MqttClientMessageQueueInterceptorContext(
enqueuedApplicationMessage.SenderClientId,
ClientId,
enqueuedApplicationMessage.ApplicationMessage);
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.ApplicationMessage.QualityOfServiceLevel;
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
await SendAsync(publishPacket).ConfigureAwait(false);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
var awaiter = _packetDispatcher.AddPacketAwaiter<MqttPubAckPacket>(publishPacket.PacketIdentifier);
await SendAsync(publishPacket).ConfigureAwait(false);
await awaiter.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
using (var awaiter1 = _packetDispatcher.AddPacketAwaiter<MqttPubRecPacket>(publishPacket.PacketIdentifier))
using (var awaiter2 = _packetDispatcher.AddPacketAwaiter<MqttPubCompPacket>(publishPacket.PacketIdentifier))
{
await SendAsync(publishPacket).ConfigureAwait(false);
await awaiter1.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
await SendAsync(new MqttPubRelPacket { PacketIdentifier = publishPacket.PacketIdentifier }).ConfigureAwait(false);
await awaiter2.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
}
}
_logger.Verbose("Enqueued application message sent (ClientId: {0}).", ClientId);
// TODO:
//Interlocked.Increment(ref _sentPacketsCount);
}
}
catch (Exception exception)
{
if (exception is MqttCommunicationTimedOutException)
{
_logger.Warning(exception, "Sending publish packet failed: Timeout (ClientId: {0}).", ClientId);
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Sending publish packet failed: Communication exception (ClientId: {0}).", ClientId);
}
else if (exception is OperationCanceledException && _cancellationToken.Token.IsCancellationRequested)
{
// The cancellation was triggered externally.
}
else
{
_logger.Error(exception, "Sending publish packet failed (ClientId: {0}).", ClientId);
}
if (publishPacket?.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
enqueuedApplicationMessage.IsDuplicate = true;
Session.ApplicationMessagesQueue.Enqueue(enqueuedApplicationMessage);
}
if (!_cancellationToken.Token.IsCancellationRequested)
{
await StopAsync().ConfigureAwait(false);
}
}
}
private async Task SendAsync(MqttBasePacket packet)
{
await _channelAdapter.SendPacketAsync(packet, _serverOptions.DefaultCommunicationTimeout, _cancellationToken.Token).ConfigureAwait(false);
Interlocked.Increment(ref _receivedPacketsCount);
if (packet is MqttPublishPacket)
{
Interlocked.Increment(ref _receivedApplicationMessagesCount);
}
}
private void OnAdapterReadingPacketCompleted(object sender, EventArgs e)
{
_keepAliveMonitor?.Resume();
}
private void OnAdapterReadingPacketStarted(object sender, EventArgs e)
{
_keepAliveMonitor?.Pause();
}
}
}
@@ -3,6 +3,7 @@
public enum MqttClientDisconnectType
{
Clean,
NotClean
NotClean,
Takeover
}
}
@@ -4,14 +4,14 @@ namespace MQTTnet.Server
{
public class MqttClientDisconnectedEventArgs : EventArgs
{
public MqttClientDisconnectedEventArgs(string clientId, bool wasCleanDisconnect)
public MqttClientDisconnectedEventArgs(string clientId, MqttClientDisconnectType disconnectType)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
WasCleanDisconnect = wasCleanDisconnect;
DisconnectType = disconnectType;
}
public string ClientId { get; }
public bool WasCleanDisconnect { get; }
public MqttClientDisconnectType DisconnectType { get; }
}
}
@@ -10,7 +10,6 @@ namespace MQTTnet.Server
public class MqttClientKeepAliveMonitor
{
private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch();
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch();
private readonly IMqttClientSession _clientSession;
private readonly IMqttNetChildLogger _logger;
@@ -26,10 +25,6 @@ namespace MQTTnet.Server
_logger = logger.CreateChildLogger(nameof(MqttClientKeepAliveMonitor));
}
public TimeSpan LastPacketReceived => _lastPacketReceivedTracker.Elapsed;
public TimeSpan LastNonKeepAlivePacketReceived => _lastNonKeepAlivePacketReceivedTracker.Elapsed;
public void Start(int keepAlivePeriod, CancellationToken cancellationToken)
{
if (keepAlivePeriod == 0)
@@ -50,20 +45,9 @@ namespace MQTTnet.Server
_isPaused = false;
}
public void Reset()
public void PacketReceived()
{
_lastPacketReceivedTracker.Restart();
_lastNonKeepAlivePacketReceivedTracker.Restart();
}
public void PacketReceived(MqttBasePacket packet)
{
_lastPacketReceivedTracker.Restart();
if (!(packet is MqttPingReqPacket))
{
_lastNonKeepAlivePacketReceivedTracker.Restart();
}
}
private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken)
@@ -71,7 +55,6 @@ namespace MQTTnet.Server
try
{
_lastPacketReceivedTracker.Restart();
_lastNonKeepAlivePacketReceivedTracker.Restart();
while (!cancellationToken.IsCancellationRequested)
{
@@ -81,7 +64,7 @@ namespace MQTTnet.Server
if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds >= keepAlivePeriod * 1.5D)
{
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientSession.ClientId);
await _clientSession.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false);
await _clientSession.StopAsync().ConfigureAwait(false);
return;
}
-481
View File
@@ -1,481 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.MessageStream;
using MQTTnet.PacketDispatcher;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public class MqttClientSession : IMqttClientSession
{
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
private readonly MqttServerEventDispatcher _eventDispatcher;
private readonly MqttClientKeepAliveMonitor _keepAliveMonitor;
private readonly MqttClientSessionPendingMessagesQueue _pendingMessagesQueue;
private readonly MqttClientSubscriptionsManager _subscriptionsManager;
private readonly MqttClientSessionsManager _sessionsManager;
private readonly IMqttNetChildLogger _logger;
private readonly IMqttServerOptions _options;
private CancellationTokenSource _cancellationTokenSource;
private MqttApplicationMessage _willMessage;
private bool _wasCleanDisconnect;
private Task _workerTask;
private IMqttChannelAdapter _channelAdapter;
private long _receivedMessagesCount;
private bool _isCleanSession = true;
public MqttClientSession(
string clientId,
IMqttServerOptions options,
MqttClientSessionsManager sessionsManager,
MqttRetainedMessagesManager retainedMessagesManager,
MqttServerEventDispatcher eventDispatcher,
IMqttNetChildLogger logger)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
_eventDispatcher = eventDispatcher ?? throw new ArgumentNullException(nameof(eventDispatcher));
ClientId = clientId;
_logger = logger.CreateChildLogger(nameof(MqttClientSession));
_keepAliveMonitor = new MqttClientKeepAliveMonitor(this, _logger);
_subscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, eventDispatcher);
_pendingMessagesQueue = new MqttClientSessionPendingMessagesQueue(_options, this, _packetDispatcher, _logger);
}
public string ClientId { get; }
public void FillStatus(MqttClientSessionStatus status)
{
status.ClientId = ClientId;
status.IsConnected = _cancellationTokenSource != null;
status.Endpoint = _channelAdapter?.Endpoint;
status.ProtocolVersion = _channelAdapter?.PacketFormatterAdapter?.ProtocolVersion;
status.PendingApplicationMessagesCount = _pendingMessagesQueue.Count;
status.ReceivedApplicationMessagesCount = _pendingMessagesQueue.SentMessagesCount;
status.SentApplicationMessagesCount = Interlocked.Read(ref _receivedMessagesCount);
status.LastPacketReceived = _keepAliveMonitor.LastPacketReceived;
status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived;
}
public async Task StopAsync(MqttClientDisconnectType type)
{
StopInternal(type);
var task = _workerTask;
if (task != null && !task.IsCompleted)
{
await task.ConfigureAwait(false);
}
}
public async Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
var topicFiltersCollection = topicFilters.ToList();
var packet = new MqttSubscribePacket();
packet.TopicFilters.AddRange(topicFiltersCollection);
await _subscriptionsManager.SubscribeAsync(packet).ConfigureAwait(false);
await EnqueueSubscribedRetainedMessagesAsync(topicFiltersCollection).ConfigureAwait(false);
}
public Task UnsubscribeAsync(IEnumerable<string> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
var packet = new MqttUnsubscribePacket();
packet.TopicFilters.AddRange(topicFilters);
_subscriptionsManager.Unsubscribe(packet);
return Task.FromResult(0);
}
public void ClearPendingApplicationMessages()
{
_pendingMessagesQueue.Clear();
}
public void Dispose()
{
_pendingMessagesQueue?.Dispose();
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
{
if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket));
if (channelAdapter == null) throw new ArgumentNullException(nameof(channelAdapter));
_workerTask = RunInternalAsync(connectPacket, channelAdapter);
return _workerTask;
}
public async Task EnqueueApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage, bool isRetainedApplicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
var checkSubscriptionsResult = _subscriptionsManager.CheckSubscriptions(applicationMessage.Topic, applicationMessage.QualityOfServiceLevel);
if (!checkSubscriptionsResult.IsSubscribed)
{
return;
}
var publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(applicationMessage);
publishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel;
// Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9].
publishPacket.Retain = isRetainedApplicationMessage;
if (publishPacket.QualityOfServiceLevel > 0)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
}
if (_options.ClientMessageQueueInterceptor != null)
{
var context = new MqttClientMessageQueueInterceptorContext(
senderClientSession?.ClientId,
ClientId,
applicationMessage);
if (_options.ClientMessageQueueInterceptor != null)
{
await _options.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.ApplicationMessage.QualityOfServiceLevel;
}
_pendingMessagesQueue.Enqueue(publishPacket);
}
private async Task RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
{
if (channelAdapter == null) throw new ArgumentNullException(nameof(channelAdapter));
try
{
_logger.Info("Client '{0}': Connected.", ClientId);
_eventDispatcher.OnClientConnected(ClientId);
_channelAdapter = channelAdapter;
_channelAdapter.ReadingPacketStarted += OnAdapterReadingPacketStarted;
_channelAdapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted;
var cancellationTokenSource = new CancellationTokenSource();
_cancellationTokenSource = cancellationTokenSource;
_wasCleanDisconnect = false;
_willMessage = connectPacket.WillMessage;
_pendingMessagesQueue.Start(channelAdapter, cancellationTokenSource.Token);
_keepAliveMonitor.Start(connectPacket.KeepAlivePeriod, cancellationTokenSource.Token);
await channelAdapter.SendPacketAsync(
new MqttConnAckPacket
{
ReturnCode = MqttConnectReturnCode.ConnectionAccepted,
ReasonCode = MqttConnectReasonCode.Success,
IsSessionPresent = _isCleanSession
},
cancellationTokenSource.Token).ConfigureAwait(false);
_isCleanSession = false;
Task.Run(async () =>
{
while (!cancellationTokenSource.IsCancellationRequested)
{
var packet = _packetDispatcher.Take(cancellationTokenSource.Token);
await ProcessReceivedPacketAsync(packet, cancellationTokenSource.Token).ConfigureAwait(false);
}
}, cancellationTokenSource.Token);
Task.Run(async () =>
{
while (!cancellationTokenSource.IsCancellationRequested)
{
try
{
var packet = await _outboundMessageStream.TakeAsync(cancellationTokenSource.Token);
await channelAdapter.SendPacketAsync(packet, cancellationTokenSource.Token);
}
catch (Exception e)
{
_logger.Error(e, "sdfsdf");
await StopAsync(MqttClientDisconnectType.NotClean);
}
}
},cancellationTokenSource.Token);
while (!cancellationTokenSource.IsCancellationRequested)
{
var packet = await channelAdapter.ReceivePacketAsync(TimeSpan.Zero, cancellationTokenSource.Token).ConfigureAwait(false);
if (packet != null)
{
_keepAliveMonitor.PacketReceived(packet);
_packetDispatcher.Dispatch(packet);
}
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
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);
}
StopInternal(MqttClientDisconnectType.NotClean);
}
finally
{
if (_willMessage != null && !_wasCleanDisconnect)
{
_sessionsManager.EnqueueApplicationMessage(this, _willMessage);
}
_willMessage = null;
_channelAdapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted;
_channelAdapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted;
_channelAdapter = null;
_logger.Info("Client '{0}': Session stopped.", ClientId);
_eventDispatcher.OnClientDisconnected(ClientId, _wasCleanDisconnect);
_workerTask = null;
}
}
private void StopInternal(MqttClientDisconnectType type)
{
var cts = _cancellationTokenSource;
if (cts == null || cts.IsCancellationRequested)
{
return;
}
_wasCleanDisconnect = type == MqttClientDisconnectType.Clean;
_cancellationTokenSource?.Cancel(false);
_packetDispatcher.Reset();
}
private readonly MqttMessageStream _outboundMessageStream = new MqttMessageStream();
private Task ProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
{
if (packet is MqttPublishPacket publishPacket)
{
return HandleIncomingPublishPacketAsync(publishPacket, cancellationToken);
}
if (packet is MqttPingReqPacket)
{
//return channelAdapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken);
_outboundMessageStream.Enqueue(new MqttPingRespPacket());
return Task.FromResult(0);
}
if (packet is MqttSubscribePacket subscribePacket)
{
return HandleIncomingSubscribePacketAsync(subscribePacket, cancellationToken);
}
if (packet is MqttUnsubscribePacket unsubscribePacket)
{
return HandleIncomingUnsubscribePacketAsync(unsubscribePacket, cancellationToken);
}
if (packet is MqttDisconnectPacket)
{
StopInternal(MqttClientDisconnectType.Clean);
return Task.FromResult(0);
}
//if (packet is MqttAuthPacket ||
// packet is MqttSubAckPacket ||
// packet is MqttUnsubAckPacket ||
// packet is MqttPubAckPacket ||
// packet is MqttPubCompPacket ||
// packet is MqttPubRecPacket ||
// packet is MqttPubRelPacket)
//{
// _packetDispatcher.TryDispatch(packet);
// return Task.FromResult(0);
//}
_logger.Warning(null, "Client '{0}': Received invalid packet ({1}). Closing connection.", ClientId, packet);
StopInternal(MqttClientDisconnectType.NotClean);
return Task.FromResult(0);
}
private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters)
{
var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false);
foreach (var applicationMessage in retainedMessages)
{
await EnqueueApplicationMessageAsync(null, applicationMessage, true).ConfigureAwait(false);
}
}
private async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
{
var subscribeResult = await _subscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false);
_outboundMessageStream.Enqueue(subscribeResult.ResponsePacket);
//await adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false);
// TODO: Add "WaitForDelivery".
if (subscribeResult.CloseConnection)
{
StopInternal(MqttClientDisconnectType.NotClean);
return;
}
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
}
private Task HandleIncomingUnsubscribePacketAsync(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{
var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket);
_outboundMessageStream.Enqueue(unsubscribeResult);
//return adapter.SendPacketAsync(unsubscribeResult, cancellationToken);
return Task.FromResult(0);
}
private Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
Interlocked.Increment(ref _receivedMessagesCount);
switch (publishPacket.QualityOfServiceLevel)
{
case MqttQualityOfServiceLevel.AtMostOnce:
{
return HandleIncomingPublishPacketWithQoS0Async(publishPacket);
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
return HandleIncomingPublishPacketWithQoS1Async(publishPacket, cancellationToken);
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
return HandleIncomingPublishPacketWithQoS2Async(publishPacket, cancellationToken);
}
default:
{
throw new MqttCommunicationException("Received a not supported QoS level.");
}
}
}
private Task HandleIncomingPublishPacketWithQoS0Async(MqttPublishPacket publishPacket)
{
_sessionsManager.EnqueueApplicationMessage(
this,
_channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket));
return Task.FromResult(0);
}
private Task HandleIncomingPublishPacketWithQoS1Async(
MqttPublishPacket publishPacket,
CancellationToken cancellationToken)
{
_sessionsManager.EnqueueApplicationMessage(
this,
_channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket));
var response = new MqttPubAckPacket
{
PacketIdentifier = publishPacket.PacketIdentifier,
ReasonCode = MqttPubAckReasonCode.Success
};
_outboundMessageStream.Enqueue(response);
//return adapter.SendPacketAsync(response, cancellationToken);
return Task.FromResult(0);
}
private async Task HandleIncomingPublishPacketWithQoS2Async(
MqttPublishPacket publishPacket,
CancellationToken cancellationToken)
{
// QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery)
_sessionsManager.EnqueueApplicationMessage(this, _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket));
using (var pubRelPacketAwaiter = _packetDispatcher.AddPacketAwaiter<MqttPubRelPacket>(publishPacket.PacketIdentifier))
{
var pubRecPacket = new MqttPubRecPacket
{
PacketIdentifier = publishPacket.PacketIdentifier,
ReasonCode = MqttPubRecReasonCode.Success
};
//await adapter.SendPacketAsync(pubRecPacket, cancellationToken).ConfigureAwait(false);
_outboundMessageStream.Enqueue(pubRecPacket);
await pubRelPacketAwaiter.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
}
}
private void OnAdapterReadingPacketCompleted(object sender, EventArgs e)
{
_keepAliveMonitor?.Resume();
}
private void OnAdapterReadingPacketStarted(object sender, EventArgs e)
{
_keepAliveMonitor?.Pause();
}
}
}
@@ -0,0 +1,117 @@
using MQTTnet.Internal;
using MQTTnet.Protocol;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public class MqttPendingApplicationMessage
{
public MqttApplicationMessage ApplicationMessage { get; set; }
public string SenderClientId { get; set; }
public bool IsRetainedMessage { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public bool IsDuplicate { get; set; }
}
public class MqttClientSessionApplicationMessagesQueue : IDisposable
{
private readonly Queue<MqttPendingApplicationMessage> _messageQueue = new Queue<MqttPendingApplicationMessage>();
private readonly AsyncAutoResetEvent _messageQueueLock = new AsyncAutoResetEvent();
private readonly IMqttServerOptions _options;
public MqttClientSessionApplicationMessagesQueue(IMqttServerOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
}
public int Count
{
get
{
lock (_messageQueue)
{
return _messageQueue.Count;
}
}
}
public void Enqueue(MqttApplicationMessage applicationMessage, string senderClientId, MqttQualityOfServiceLevel qualityOfServiceLevel, bool isRetainedMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
Enqueue(new MqttPendingApplicationMessage
{
ApplicationMessage = applicationMessage,
SenderClientId = senderClientId,
QualityOfServiceLevel = qualityOfServiceLevel,
IsRetainedMessage = isRetainedMessage
});
}
public void Clear()
{
lock (_messageQueue)
{
_messageQueue.Clear();
}
}
public void Dispose()
{
}
public async Task<MqttPendingApplicationMessage> TakeAsync(CancellationToken cancellationToken)
{
// TODO: Create a blocking queue from this.
while (!cancellationToken.IsCancellationRequested)
{
lock (_messageQueue)
{
if (_messageQueue.Count > 0)
{
return _messageQueue.Dequeue();
}
}
await _messageQueueLock.WaitOneAsync(cancellationToken).ConfigureAwait(false);
}
return null;
}
public void Enqueue(MqttPendingApplicationMessage enqueuedApplicationMessage)
{
if (enqueuedApplicationMessage == null) throw new ArgumentNullException(nameof(enqueuedApplicationMessage));
lock (_messageQueue)
{
if (_messageQueue.Count >= _options.MaxPendingMessagesPerClient)
{
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
{
return;
}
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
{
_messageQueue.Dequeue();
}
}
_messageQueue.Enqueue(enqueuedApplicationMessage);
}
_messageQueueLock.Set();
}
}
}
@@ -1,212 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.PacketDispatcher;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public class MqttClientSessionPendingMessagesQueue : IDisposable
{
private readonly Queue<MqttPublishPacket> _queue = new Queue<MqttPublishPacket>();
private readonly AsyncAutoResetEvent _queueLock = new AsyncAutoResetEvent();
private readonly IMqttServerOptions _options;
private readonly MqttClientSession _clientSession;
private readonly MqttPacketDispatcher _packetDispatcher;
private readonly IMqttNetChildLogger _logger;
private long _sentPacketsCount;
public MqttClientSessionPendingMessagesQueue(
IMqttServerOptions options,
MqttClientSession clientSession,
MqttPacketDispatcher packetDispatcher,
IMqttNetChildLogger logger)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession));
_packetDispatcher = packetDispatcher ?? throw new ArgumentNullException(nameof(packetDispatcher));
_logger = logger.CreateChildLogger(nameof(MqttClientSessionPendingMessagesQueue));
}
public int Count
{
get
{
lock (_queue)
{
return _queue.Count;
}
}
}
public long SentMessagesCount => Interlocked.Read(ref _sentPacketsCount);
public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
{
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
if (cancellationToken.IsCancellationRequested)
{
return;
}
Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken);
}
public void Enqueue(MqttPublishPacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
lock (_queue)
{
if (_queue.Count >= _options.MaxPendingMessagesPerClient)
{
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
{
return;
}
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
{
_queue.Dequeue();
}
}
_queue.Enqueue(packet);
}
_queueLock.Set();
_logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId);
}
public void Clear()
{
lock (_queue)
{
_queue.Clear();
}
}
public void Dispose()
{
}
private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
await TrySendNextQueuedPacketAsync(adapter, cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error(exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _clientSession.ClientId);
}
}
private async Task TrySendNextQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
{
MqttPublishPacket packet = null;
try
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
lock (_queue)
{
if (_queue.Count > 0)
{
packet = _queue.Dequeue();
}
}
if (packet == null)
{
await _queueLock.WaitOneAsync(cancellationToken).ConfigureAwait(false);
return;
}
if (packet.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false);
}
else if (packet.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
var awaiter = _packetDispatcher.AddPacketAwaiter<MqttPubAckPacket>(packet.PacketIdentifier);
await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false);
await awaiter.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
}
else if (packet.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
var awaiter1 = _packetDispatcher.AddPacketAwaiter<MqttPubRecPacket>(packet.PacketIdentifier);
var awaiter2 = _packetDispatcher.AddPacketAwaiter<MqttPubCompPacket>(packet.PacketIdentifier);
try
{
await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false);
await awaiter1.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
await adapter.SendPacketAsync(new MqttPubRelPacket { PacketIdentifier = packet.PacketIdentifier }, cancellationToken).ConfigureAwait(false);
await awaiter2.WaitOneAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
}
finally
{
_packetDispatcher.RemovePacketAwaiter<MqttPubRecPacket>(packet.PacketIdentifier);
_packetDispatcher.RemovePacketAwaiter<MqttPubCompPacket>(packet.PacketIdentifier);
}
}
_logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
Interlocked.Increment(ref _sentPacketsCount);
}
catch (Exception exception)
{
if (exception is MqttCommunicationTimedOutException)
{
_logger.Warning(exception, "Sending publish packet failed: Timeout (ClientId: {0}).", _clientSession.ClientId);
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Sending publish packet failed: Communication exception (ClientId: {0}).", _clientSession.ClientId);
}
else if (exception is OperationCanceledException && cancellationToken.IsCancellationRequested)
{
}
else
{
_logger.Error(exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId);
}
if (packet?.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
packet.Dup = true;
Enqueue(packet);
}
if (!cancellationToken.IsCancellationRequested)
{
await _clientSession.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false);
}
}
}
}
}
@@ -1,52 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Formatter;
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 long PendingApplicationMessagesCount { get; set; }
public long ReceivedApplicationMessagesCount { get; set; }
public long SentApplicationMessagesCount { get; set; }
public Task DisconnectAsync()
{
return _session.StopAsync(MqttClientDisconnectType.NotClean);
}
public async Task DeleteSessionAsync()
{
try
{
await _session.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false);
}
finally
{
await _sessionsManager.DeleteSessionAsync(ClientId).ConfigureAwait(false);
}
}
public Task ClearPendingApplicationMessagesAsync()
{
_session.ClearPendingApplicationMessages();
return Task.FromResult(0);
}
}
}
+133 -100
View File
@@ -1,14 +1,13 @@
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.Diagnostics;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server.Status;
namespace MQTTnet.Server
{
@@ -16,9 +15,9 @@ namespace MQTTnet.Server
{
private readonly BlockingCollection<MqttEnqueuedApplicationMessage> _messageQueue = new BlockingCollection<MqttEnqueuedApplicationMessage>();
private readonly AsyncLock _sessionsLock = new AsyncLock();
private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
private readonly ConcurrentDictionary<string, MqttClientConnection> _connections = new ConcurrentDictionary<string, MqttClientConnection>();
private readonly ConcurrentDictionary<string, MqttClientSession> _sessions = new ConcurrentDictionary<string, MqttClientSession>();
private readonly CancellationToken _cancellationToken;
private readonly MqttServerEventDispatcher _eventDispatcher;
@@ -27,8 +26,8 @@ namespace MQTTnet.Server
private readonly IMqttNetChildLogger _logger;
public MqttClientSessionsManager(
IMqttServerOptions options,
MqttRetainedMessagesManager retainedMessagesManager,
IMqttServerOptions options,
MqttRetainedMessagesManager retainedMessagesManager,
CancellationToken cancellationToken,
MqttServerEventDispatcher eventDispatcher,
IMqttNetChildLogger logger)
@@ -45,98 +44,100 @@ namespace MQTTnet.Server
public void Start()
{
Task.Factory.StartNew(() => TryProcessQueuedApplicationMessagesAsync(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
Task.Run(() => TryProcessQueuedApplicationMessagesAsync(_cancellationToken), _cancellationToken);
}
public async Task StopAsync()
{
List<MqttClientSession> sessions;
using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{
sessions = _sessions.Values.ToList();
}
//using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
foreach (var session in sessions)
foreach (var connection in _connections.Values)
{
await session.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false);
await connection.StopAsync().ConfigureAwait(false);
}
}
public Task HandleConnectionAsync(IMqttChannelAdapter clientAdapter)
{
return HandleConnectionAsync(clientAdapter, _cancellationToken);
// TODO: Check if Task.Run is required.
//return Task.Run(() => HandleConnectionAsync(clientAdapter, _cancellationToken), _cancellationToken);
}
public async Task<IList<IMqttClientSessionStatus>> GetClientStatusAsync()
public Task<IList<IMqttClientStatus>> GetClientStatusAsync()
{
var result = new List<IMqttClientSessionStatus>();
var result = new List<IMqttClientStatus>();
using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
foreach (var connection in _connections.Values)
{
foreach (var session in _sessions.Values)
{
var status = new MqttClientSessionStatus(this, session);
session.FillStatus(status);
var clientStatus = new MqttClientStatus(connection, this);
connection.FillStatus(clientStatus);
var sessionStatus = new MqttSessionStatus(connection.Session, this);
connection.Session.FillStatus(sessionStatus);
clientStatus.Session = sessionStatus;
result.Add(status);
}
result.Add(clientStatus);
}
return result;
return Task.FromResult((IList<IMqttClientStatus>)result);
}
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
public Task<IList<IMqttSessionStatus>> GetSessionStatusAsync()
{
var result = new List<IMqttSessionStatus>();
foreach (var session in _sessions.Values)
{
var sessionStatus = new MqttSessionStatus(session, this);
session.FillStatus(sessionStatus);
result.Add(sessionStatus);
}
return Task.FromResult((IList<IMqttSessionStatus>)result);
}
public void DispatchApplicationMessage(MqttApplicationMessage applicationMessage, MqttClientConnection sender)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
_messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, applicationMessage), _cancellationToken);
_messageQueue.Add(new MqttEnqueuedApplicationMessage(applicationMessage, sender), _cancellationToken);
}
public async Task SubscribeAsync(string clientId, IEnumerable<TopicFilter> topicFilters)
public Task SubscribeAsync(string clientId, ICollection<TopicFilter> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
if (!_sessions.TryGetValue(clientId, out var session))
{
if (!_sessions.TryGetValue(clientId, out var session))
{
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
}
await session.SubscribeAsync(topicFilters).ConfigureAwait(false);
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
}
return session.SubscribeAsync(topicFilters, _retainedMessagesManager);
}
public async Task UnsubscribeAsync(string clientId, IEnumerable<string> topicFilters)
public Task UnsubscribeAsync(string clientId, IEnumerable<string> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
if (!_sessions.TryGetValue(clientId, out var session))
{
if (!_sessions.TryGetValue(clientId, out var session))
{
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
}
await session.UnsubscribeAsync(topicFilters).ConfigureAwait(false);
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
}
return session.UnsubscribeAsync(topicFilters);
}
public async Task DeleteSessionAsync(string clientId)
{
using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
if (_connections.TryGetValue(clientId, out var connection))
{
if (_sessions.TryGetValue(clientId, out var session))
{
session.Dispose();
}
await connection.StopAsync();
}
_sessions.Remove(clientId);
if (_sessions.TryRemove(clientId, out var session))
{
}
_logger.Verbose("Session for client '{0}' deleted.", clientId);
@@ -179,7 +180,7 @@ namespace MQTTnet.Server
{
if (interceptorContext.CloseConnection)
{
await enqueuedApplicationMessage.Sender.StopAsync(MqttClientDisconnectType.NotClean).ConfigureAwait(false);
await enqueuedApplicationMessage.Sender.StopAsync().ConfigureAwait(false);
}
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
@@ -197,15 +198,12 @@ namespace MQTTnet.Server
await _retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).ConfigureAwait(false);
}
using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
foreach (var clientSession in _sessions.Values)
{
foreach (var clientSession in _sessions.Values)
{
await clientSession.EnqueueApplicationMessageAsync(
enqueuedApplicationMessage.Sender,
enqueuedApplicationMessage.ApplicationMessage,
false).ConfigureAwait(false);
}
clientSession.EnqueueApplicationMessage(
enqueuedApplicationMessage.ApplicationMessage,
sender?.ClientId,
false);
}
}
catch (OperationCanceledException)
@@ -219,6 +217,7 @@ namespace MQTTnet.Server
private async Task HandleConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
{
var disconnectType = MqttClientDisconnectType.NotClean;
var clientId = string.Empty;
try
@@ -232,22 +231,31 @@ namespace MQTTnet.Server
clientId = connectPacket.ClientId;
var connectReturnCode = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false);
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
var validatorContext = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false);
if (validatorContext.ReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
// TODO: Move to channel adapter data converter.
// Send failure response here without preparing a session. The result for a successful connect
// will be sent from the session itself.
await channelAdapter.SendPacketAsync(
new MqttConnAckPacket
{
ReturnCode = connectReturnCode,
ReturnCode = validatorContext.ReturnCode,
ReasonCode = MqttConnectReasonCode.NotAuthorized
},
_options.DefaultCommunicationTimeout,
cancellationToken).ConfigureAwait(false);
return;
}
var session = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false);
await session.RunAsync(connectPacket, channelAdapter).ConfigureAwait(false);
var connection = await CreateConnectionAsync(channelAdapter, connectPacket).ConfigureAwait(false);
_eventDispatcher.OnClientConnected(clientId);
disconnectType = await connection.RunAsync().ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -258,26 +266,29 @@ namespace MQTTnet.Server
}
finally
{
_connections.TryRemove(clientId, out _);
////connection?.ReferenceCounter.Decrement();
////if (connection?.ReferenceCounter.HasReferences == true)
////{
//// disconnectType = MqttClientDisconnectType.Takeover;
////}
////else
{
if (!_options.EnablePersistentSessions)
{
await DeleteSessionAsync(clientId).ConfigureAwait(false);
}
}
await TryCleanupChannelAsync(channelAdapter).ConfigureAwait(false);
if (!_options.EnablePersistentSessions)
{
// TODO: Check if the session will be used later.
// Consider reference counter or "Recycle" property
// Or add timer (will be required for MQTTv5 (session life time) "IsActiveProperty".
//öö
//await DeleteSessionAsync(clientId).ConfigureAwait(false);
}
_eventDispatcher.OnClientDisconnected(clientId, disconnectType);
}
}
private async Task<MqttConnectReturnCode> ValidateConnectionAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
private async Task<MqttConnectionValidatorContext> ValidateConnectionAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
{
if (_options.ConnectionValidator == null)
{
return MqttConnectReturnCode.ConnectionAccepted;
}
var context = new MqttConnectionValidatorContext(
connectPacket.ClientId,
connectPacket.Username,
@@ -285,47 +296,69 @@ namespace MQTTnet.Server
connectPacket.WillMessage,
clientAdapter.Endpoint);
await _options.ConnectionValidator.ValidateConnection(context).ConfigureAwait(false);
return context.ReturnCode;
var connectionValidator = _options.ConnectionValidator;
if (connectionValidator == null)
{
context.ReturnCode = MqttConnectReturnCode.ConnectionAccepted;
return context;
}
await connectionValidator.ValidateConnectionAsync(context).ConfigureAwait(false);
return context;
}
private async Task<MqttClientSession> PrepareClientSessionAsync(MqttConnectPacket connectPacket)
private readonly SemaphoreSlim _createConnectionGate = new SemaphoreSlim(1, 1);
private async Task<MqttClientConnection> CreateConnectionAsync(IMqttChannelAdapter channelAdapter, MqttConnectPacket connectPacket)
{
using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
await _createConnectionGate.WaitAsync(_cancellationToken).ConfigureAwait(false);
try
{
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var session);
var isConnectionPresent = _connections.TryGetValue(connectPacket.ClientId, out var existingConnection);
if (isConnectionPresent)
{
await existingConnection.StopAsync().ConfigureAwait(false);
}
if (isSessionPresent)
{
if (connectPacket.CleanSession)
{
await clientSession.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false);
clientSession.Dispose();
clientSession = null;
_logger.Verbose("Stopped existing session of client '{0}'.", connectPacket.ClientId);
// TODO: Check if required.
//session.Dispose();
session = null;
_logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId);
}
else
{
await clientSession.StopAsync(MqttClientDisconnectType.Clean).ConfigureAwait(false);
_logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
}
}
if (clientSession == null)
if (session == null)
{
clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _retainedMessagesManager, _eventDispatcher, _logger);
_sessions[connectPacket.ClientId] = clientSession;
session = new MqttClientSession(connectPacket.ClientId, _eventDispatcher, _options);
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
}
return clientSession;
var connection = new MqttClientConnection(connectPacket, channelAdapter, session, _options, this, _retainedMessagesManager, _logger);
_connections[connection.ClientId] = connection;
_sessions[session.ClientId] = session;
return connection;
}
finally
{
_createConnectionGate.Release();
}
}
private async Task<MqttApplicationMessageInterceptorContext> InterceptApplicationMessageAsync(MqttClientSession sender, MqttApplicationMessage applicationMessage)
private async Task<MqttApplicationMessageInterceptorContext> InterceptApplicationMessageAsync(MqttClientConnection sender, MqttApplicationMessage applicationMessage)
{
var interceptor = _options.ApplicationMessageInterceptor;
if (interceptor == null)
@@ -14,7 +14,7 @@ namespace MQTTnet.Server
private readonly MqttServerEventDispatcher _eventDispatcher;
private readonly string _clientId;
public MqttClientSubscriptionsManager(string clientId, IMqttServerOptions options, MqttServerEventDispatcher eventDispatcher)
public MqttClientSubscriptionsManager(string clientId, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions options)
{
_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
_options = options ?? throw new ArgumentNullException(nameof(options));
@@ -68,7 +68,29 @@ namespace MQTTnet.Server
return result;
}
public MqttUnsubAckPacket Unsubscribe(MqttUnsubscribePacket unsubscribePacket)
public async Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
{
foreach (var topicFilter in topicFilters)
{
var interceptorContext = await InterceptSubscribeAsync(topicFilter).ConfigureAwait(false);
if (!interceptorContext.AcceptSubscription)
{
continue;
}
if (interceptorContext.AcceptSubscription)
{
lock (_subscriptions)
{
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
}
_eventDispatcher.OnClientSubscribedTopic(_clientId, topicFilter);
}
}
}
public Task<MqttUnsubAckPacket> UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket)
{
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
@@ -94,7 +116,22 @@ namespace MQTTnet.Server
}
}
return unsubAckPacket;
return Task.FromResult(unsubAckPacket);
}
public Task UnsubscribeAsync(IEnumerable<string> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
lock (_subscriptions)
{
foreach (var topicFilter in topicFilters)
{
_subscriptions.Remove(topicFilter);
}
}
return Task.FromResult(0);
}
public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel)
@@ -2,13 +2,13 @@
{
public class MqttEnqueuedApplicationMessage
{
public MqttEnqueuedApplicationMessage(MqttClientSession sender, MqttApplicationMessage applicationMessage)
public MqttEnqueuedApplicationMessage(MqttApplicationMessage applicationMessage, MqttClientConnection sender)
{
Sender = sender;
ApplicationMessage = applicationMessage;
}
public MqttClientSession Sender { get; }
public MqttClientConnection Sender { get; }
public MqttApplicationMessage ApplicationMessage { get; }
}
@@ -101,29 +101,34 @@ namespace MQTTnet.Server
public async Task<List<MqttApplicationMessage>> GetSubscribedMessagesAsync(ICollection<TopicFilter> topicFilters)
{
var retainedMessages = new List<MqttApplicationMessage>();
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
var matchingRetainedMessages = new List<MqttApplicationMessage>();
List<MqttApplicationMessage> retainedMessages;
using (await _messagesLock.WaitAsync().ConfigureAwait(false))
{
foreach (var retainedMessage in _messages.Values)
{
foreach (var topicFilter in topicFilters)
{
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
{
continue;
}
retainedMessages.Add(retainedMessage);
break;
}
}
retainedMessages = _messages.Values.ToList();
}
return retainedMessages;
foreach (var retainedMessage in retainedMessages)
{
foreach (var topicFilter in topicFilters)
{
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
{
continue;
}
matchingRetainedMessages.Add(retainedMessage);
break;
}
}
return matchingRetainedMessages;
}
public async Task<List<MqttApplicationMessage>> GetMessagesAsync()
public async Task<IList<MqttApplicationMessage>> GetMessagesAsync()
{
using (await _messagesLock.WaitAsync().ConfigureAwait(false))
{
+13 -7
View File
@@ -7,6 +7,7 @@ using MQTTnet.Adapter;
using MQTTnet.Client.Publishing;
using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using MQTTnet.Server.Status;
namespace MQTTnet.Server
{
@@ -59,17 +60,22 @@ namespace MQTTnet.Server
public IMqttServerOptions Options { get; private set; }
public Task<IList<IMqttClientSessionStatus>> GetClientSessionsStatusAsync()
public Task<IList<IMqttClientStatus>> GetClientStatusAsync()
{
return _clientSessionsManager.GetClientStatusAsync();
}
public IList<MqttApplicationMessage> GetRetainedMessages()
public Task<IList<IMqttSessionStatus>> GetSessionStatusAsync()
{
return _retainedMessagesManager.GetMessagesAsync().GetAwaiter().GetResult();
return _clientSessionsManager.GetSessionStatusAsync();
}
public Task SubscribeAsync(string clientId, IEnumerable<TopicFilter> topicFilters)
public Task<IList<MqttApplicationMessage>> GetRetainedMessagesAsync()
{
return _retainedMessagesManager.GetMessagesAsync();
}
public Task SubscribeAsync(string clientId, ICollection<TopicFilter> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
@@ -77,7 +83,7 @@ namespace MQTTnet.Server
return _clientSessionsManager.SubscribeAsync(clientId, topicFilters);
}
public Task UnsubscribeAsync(string clientId, IEnumerable<string> topicFilters)
public Task UnsubscribeAsync(string clientId, ICollection<string> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
@@ -85,13 +91,13 @@ namespace MQTTnet.Server
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
}
public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage)
public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started.");
_clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage);
_clientSessionsManager.DispatchApplicationMessage(applicationMessage, null);
return Task.FromResult(new MqttClientPublishResult());
}
@@ -23,7 +23,7 @@ namespace MQTTnet.Server
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
}
public Task ValidateConnection(MqttConnectionValidatorContext context)
public Task ValidateConnectionAsync(MqttConnectionValidatorContext context)
{
return _callback(context);
}
@@ -24,9 +24,9 @@ namespace MQTTnet.Server
ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter));
}
public void OnClientDisconnected(string clientId, bool wasCleanDisconnect)
public void OnClientDisconnected(string clientId, MqttClientDisconnectType disconnectType)
{
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientId, wasCleanDisconnect));
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientId, disconnectType));
}
public void OnApplicationMessageReceived(string senderClientId, MqttApplicationMessage applicationMessage)
@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Client.Publishing;
using MQTTnet.Protocol;
namespace MQTTnet.Server
@@ -41,5 +45,89 @@ namespace MQTTnet.Server
return server.UnsubscribeAsync(clientId, topicFilters);
}
public static async Task PublishAsync(this IMqttServer server, IEnumerable<MqttApplicationMessage> applicationMessages)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await server.PublishAsync(applicationMessage).ConfigureAwait(false);
}
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, MqttApplicationMessage applicationMessage)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
return server.PublishAsync(applicationMessage, CancellationToken.None);
}
public static async Task PublishAsync(this IMqttServer server, params MqttApplicationMessage[] applicationMessages)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await server.PublishAsync(applicationMessage, CancellationToken.None).ConfigureAwait(false);
}
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, string topic)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return server.PublishAsync(builder => builder
.WithTopic(topic));
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, string topic, string payload)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return server.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload));
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return server.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel));
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return server.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.WithRetainFlag(retain));
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder, CancellationToken cancellationToken)
{
var message = builder(new MqttApplicationMessageBuilder()).Build();
return server.PublishAsync(message, cancellationToken);
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder)
{
var message = builder(new MqttApplicationMessageBuilder()).Build();
return server.PublishAsync(message, CancellationToken.None);
}
}
}
@@ -4,6 +4,6 @@
{
public bool IsExistingSession { get; set; }
public MqttClientSession Session { get; set; }
public MqttClientConnection Session { get; set; }
}
}
@@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Formatter;
namespace MQTTnet.Server.Status
{
public interface IMqttClientStatus
{
string ClientId { get; }
string Endpoint { get; }
MqttProtocolVersion ProtocolVersion { get; }
DateTime LastPacketReceivedTimestamp { get; }
DateTime LastNonKeepAlivePacketReceivedTimestamp { get; }
long ReceivedApplicationMessagesCount { get; }
long SentApplicationMessagesCount { get; }
long ReceivedPacketsCount { get; }
long SentPacketsCount { get; }
IMqttSessionStatus Session { get; }
Task DisconnectAsync();
}
}
@@ -0,0 +1,17 @@
using System.Threading.Tasks;
namespace MQTTnet.Server.Status
{
public interface IMqttSessionStatus
{
string ClientId { get; set; }
bool IsConnected { get; }
long PendingApplicationMessagesCount { get; set; }
Task ClearPendingApplicationMessagesAsync();
Task DeleteAsync();
}
}
@@ -0,0 +1,43 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Formatter;
namespace MQTTnet.Server.Status
{
public class MqttClientStatus : IMqttClientStatus
{
private readonly MqttClientSessionsManager _sessionsManager;
private readonly MqttClientConnection _connection;
public MqttClientStatus(MqttClientConnection connection, MqttClientSessionsManager sessionsManager)
{
_connection = connection;
_sessionsManager = sessionsManager;
}
public string ClientId { get; set; }
public string Endpoint { get; set; }
public MqttProtocolVersion ProtocolVersion { get; set; }
public DateTime LastPacketReceivedTimestamp { get; set; }
public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; set; }
public long ReceivedApplicationMessagesCount { get; set; }
public long SentApplicationMessagesCount { get; set; }
public long ReceivedPacketsCount { get; set; }
public long SentPacketsCount { get; set; }
public IMqttSessionStatus Session { get; set; }
public Task DisconnectAsync()
{
return _connection.StopAsync();
}
}
}
@@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;
namespace MQTTnet.Server.Status
{
public class MqttSessionStatus : IMqttSessionStatus
{
private readonly MqttClientSession _session;
private readonly MqttClientSessionsManager _sessionsManager;
public MqttSessionStatus(MqttClientSession session, MqttClientSessionsManager sessionsManager)
{
_session = session ?? throw new ArgumentNullException(nameof(session));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
}
public string ClientId { get; set; }
public long PendingApplicationMessagesCount { get; set; }
public bool IsConnected { get; set; }
public DateTime CreatedTimestamp { get; set; }
public Task DeleteAsync()
{
return _sessionsManager.DeleteSessionAsync(ClientId);
}
public Task ClearPendingApplicationMessagesAsync()
{
_session.ApplicationMessagesQueue.Clear();
return Task.FromResult(0);
}
}
}