mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Refactor session and connection handling in server. Fix QoS level 2 issues.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
-1
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
+3
-3
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user