mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-19 19:13:11 +08:00
Implement new managed client.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessagesManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
|
||||
readonly Queue<ManagedMqttApplicationMessage> _messageQueue = new Queue<ManagedMqttApplicationMessage>();
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
public ApplicationMessagesManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<ApplicationMessagesManager>();
|
||||
}
|
||||
|
||||
public AsyncEvent<ApplicationMessageDroppedEventArgs> ApplicationMessageDroppedEvent { get; } = new AsyncEvent<ApplicationMessageDroppedEventArgs>();
|
||||
|
||||
public AsyncEvent<ApplicationMessageEnqueueingEventArgs> ApplicationMessageEnqueueingEvent { get; } = new AsyncEvent<ApplicationMessageEnqueueingEventArgs>();
|
||||
|
||||
public int EnqueuedApplicationMessagesCount
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
return _messageQueue.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dequeue()
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
_messageQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Enqueue(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
// Fire the event first so that the handler can make sure that this message is
|
||||
// properly backed up in a suitable storage.
|
||||
if (ApplicationMessageEnqueueingEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ApplicationMessageEnqueueingEventArgs(applicationMessage, 0);
|
||||
await ApplicationMessageEnqueueingEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
lock (_messageQueue)
|
||||
{
|
||||
|
||||
// TODO: Drop if too much!
|
||||
|
||||
_messageQueue.Enqueue(applicationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPeek(out ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
if (_messageQueue.Count == 0)
|
||||
{
|
||||
applicationMessage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
applicationMessage = _messageQueue.Peek();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessageDroppedEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationMessageDroppedEventArgs(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessageEnqueueingEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationMessageEnqueueingEventArgs(ManagedMqttApplicationMessage applicationMessage, ushort packetIdentifier)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
PacketIdentifier = packetIdentifier;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
|
||||
public ushort PacketIdentifier { get; }
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessageProcessedEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationMessageProcessedEventArgs(ManagedMqttApplicationMessage applicationMessage, Exception exception)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception which was thrown while sending the message.
|
||||
/// </summary>
|
||||
public Exception Exception { get; }
|
||||
|
||||
public bool HasFailed => Exception != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ConnectingFailedEventArgs : EventArgs
|
||||
{
|
||||
public ConnectingFailedEventArgs(MqttClientConnectResult connectResult, Exception exception)
|
||||
{
|
||||
ConnectResult = connectResult;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is null when the server was not reachable and thus no MQTT response was transmitted.
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
|
||||
public Exception Exception { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedProcessFailedEventArgs : EventArgs
|
||||
{
|
||||
public ManagedProcessFailedEventArgs(Exception exception)
|
||||
{
|
||||
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
||||
}
|
||||
|
||||
public Exception Exception { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class SubscribeProcessedEventArgs : EventArgs
|
||||
{
|
||||
public SubscribeProcessedEventArgs(MqttClientSubscribeOptions options, MqttClientSubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
Result = result;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptions Options { get; }
|
||||
|
||||
public MqttClientSubscribeResultCode Result { get; }
|
||||
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class UnsubscribeProcessedEventArgs : EventArgs
|
||||
{
|
||||
public UnsubscribeProcessedEventArgs(MqttClientUnsubscribeOptions options, MqttClientUnsubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
Result = result;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public MqttClientUnsubscribeOptions Options { get; }
|
||||
|
||||
public MqttClientUnsubscribeResultCode Result { get; }
|
||||
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public enum MaintainConnectionResult
|
||||
{
|
||||
NoChange,
|
||||
|
||||
ConnectedEstablished,
|
||||
|
||||
NotConnected
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttSubscription
|
||||
{
|
||||
public ManagedMqttSubscription(MqttClientSubscribeOptions subscribeOptions)
|
||||
{
|
||||
SubscribeOptions = subscribeOptions ?? throw new ArgumentNullException(nameof(subscribeOptions));
|
||||
|
||||
Topic = subscribeOptions.TopicFilters.First().Topic;
|
||||
}
|
||||
|
||||
public bool IsObsolete => UnsubscribeOptions != null;
|
||||
|
||||
public bool IsPending { get; set; } = true;
|
||||
|
||||
public MqttClientSubscribeOptions SubscribeOptions { get; }
|
||||
|
||||
public string Topic { get; }
|
||||
|
||||
public MqttClientUnsubscribeOptions UnsubscribeOptions { get; private set; }
|
||||
|
||||
public void MarkAsUnsubscribed(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
UnsubscribeOptions = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttSubscriptionsManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
// Do not use a dictionary in order to preserve subscription ordering (retained messages).
|
||||
readonly List<ManagedMqttSubscription> _subscriptions = new List<ManagedMqttSubscription>();
|
||||
|
||||
public ManagedMqttSubscriptionsManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<ManagedMqttSubscriptionsManager>();
|
||||
}
|
||||
|
||||
public AsyncEvent<SubscribeProcessedEventArgs> SubscribeProcessedEvent { get; } = new AsyncEvent<SubscribeProcessedEventArgs>();
|
||||
|
||||
public AsyncEvent<UnsubscribeProcessedEventArgs> UnsubscribeProcessedEvent { get; } = new AsyncEvent<UnsubscribeProcessedEventArgs>();
|
||||
|
||||
public void ResubscribeAll()
|
||||
{
|
||||
lock (_subscriptions)
|
||||
{
|
||||
foreach (var subscription in _subscriptions)
|
||||
{
|
||||
subscription.IsPending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Subscribe(MqttClientSubscribeOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.TopicFilters.Count > 1)
|
||||
{
|
||||
throw new NotSupportedException("The managed client only supports subscribing to one topic at the same time.");
|
||||
}
|
||||
|
||||
var topic = options.TopicFilters.First().Topic;
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topic);
|
||||
|
||||
_subscriptions.RemoveAll(s => s.Topic.Equals(topic));
|
||||
_subscriptions.Add(new ManagedMqttSubscription(options));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Synchronize(CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Remember real changes.
|
||||
|
||||
List<ManagedMqttSubscription> pendingSubscriptions;
|
||||
List<ManagedMqttSubscription> obsoleteSubscriptions;
|
||||
lock (_subscriptions)
|
||||
{
|
||||
pendingSubscriptions = _subscriptions.Where(s => s.IsPending).ToList();
|
||||
obsoleteSubscriptions = _subscriptions.Where(s => s.IsObsolete).ToList();
|
||||
}
|
||||
|
||||
if (pendingSubscriptions.Count == 0 && obsoleteSubscriptions.Count == 0)
|
||||
{
|
||||
_logger.Verbose("No subscriptions to synchronize");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var subscription in pendingSubscriptions)
|
||||
{
|
||||
var subscribeResult = await _mqttClient.SubscribeAsync(subscription.SubscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
subscription.IsPending = false;
|
||||
await FireSubscribeProcessedEvent(subscription, subscribeResult).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var subscription in obsoleteSubscriptions)
|
||||
{
|
||||
var unsubscribeResult = await _mqttClient.UnsubscribeAsync(subscription.UnsubscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
lock (_subscriptions)
|
||||
{
|
||||
_subscriptions.Remove(subscription);
|
||||
}
|
||||
|
||||
await FireUnsubscribeProcessedEvent(subscription, unsubscribeResult).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Unsubscribe(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
var topic = options.TopicFilters.First();
|
||||
var existingSubscription = _subscriptions.FirstOrDefault(s => s.Topic.Equals(topic));
|
||||
|
||||
if (existingSubscription != null)
|
||||
{
|
||||
existingSubscription.MarkAsUnsubscribed(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task FireSubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientSubscribeResult subscribeResult)
|
||||
{
|
||||
_logger.Info("Subscribed topic '{0}'.", subscription.Topic);
|
||||
|
||||
if (SubscribeProcessedEvent.HasHandlers)
|
||||
{
|
||||
var resultCode = subscribeResult?.Items.First().ResultCode ?? MqttClientSubscribeResultCode.UnspecifiedError;
|
||||
var eventArgs = new SubscribeProcessedEventArgs(subscription.SubscribeOptions, resultCode, subscribeResult?.UserProperties);
|
||||
await SubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task FireUnsubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientUnsubscribeResult unsubscribeResult)
|
||||
{
|
||||
_logger.Info("Unsubscribed topic '{0}'.", subscription.Topic);
|
||||
|
||||
if (UnsubscribeProcessedEvent.HasHandlers)
|
||||
{
|
||||
var resultCode = unsubscribeResult?.Items.First().ResultCode ?? MqttClientUnsubscribeResultCode.UnspecifiedError;
|
||||
var eventArgs = new UnsubscribeProcessedEventArgs(subscription.UnsubscribeOptions, resultCode, unsubscribeResult?.UserProperties);
|
||||
await UnsubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class MqttConnectionManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
bool _wasConnected;
|
||||
|
||||
public MqttConnectionManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<MqttConnectionManager>();
|
||||
|
||||
_mqttClient.DisconnectedAsync += OnClientDisconnected;
|
||||
}
|
||||
|
||||
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
|
||||
|
||||
public AsyncEvent<ConnectingFailedEventArgs> ConnectingFailedEvent { get; } = new AsyncEvent<ConnectingFailedEventArgs>();
|
||||
|
||||
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
|
||||
|
||||
public async Task<MaintainConnectionResult> MaintainConnection(ManagedMqttClientOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (_wasConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
// The only valid way for testing the connection is to send a PING. So we start with this
|
||||
// in order to check if reconnecting etc. is require or not.
|
||||
await _mqttClient.PingAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// The client is properly connection. No further action required.
|
||||
return MaintainConnectionResult.NoChange;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while checking connection.");
|
||||
}
|
||||
}
|
||||
|
||||
MqttClientConnectResult connectResult = null;
|
||||
try
|
||||
{
|
||||
// The PING was not successful so that there is no working connection. So we connect.
|
||||
connectResult = await _mqttClient.ConnectAsync(options.ClientOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_wasConnected = true;
|
||||
_logger.Info("Connected.");
|
||||
|
||||
if (ConnectedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
|
||||
await ConnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return MaintainConnectionResult.ConnectedEstablished;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (!(exception is OperationCanceledException))
|
||||
{
|
||||
_logger.Error(exception, "Error while connecting.");
|
||||
}
|
||||
|
||||
if (ConnectingFailedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ConnectingFailedEventArgs(connectResult, exception);
|
||||
await ConnectingFailedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
_wasConnected = false;
|
||||
return MaintainConnectionResult.NotConnected;
|
||||
}
|
||||
|
||||
Task OnClientDisconnected(MqttClientDisconnectedEventArgs arg)
|
||||
{
|
||||
_wasConnected = false;
|
||||
return DisconnectedEvent?.InvokeAsync(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class NewManagedMqttClient : Disposable, IManagedMqttClient
|
||||
{
|
||||
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
|
||||
readonly ApplicationMessagesManager _applicationMessagesManager;
|
||||
readonly MqttConnectionManager _connectionManager;
|
||||
readonly AsyncEvent<EventArgs> _isConnectedChangedEvent = new AsyncEvent<EventArgs>();
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly ManagedMqttSubscriptionsManager _subscriptionsManager;
|
||||
|
||||
readonly ManualResetEventSlim _workSignal = new ManualResetEventSlim();
|
||||
|
||||
CancellationTokenSource _workerCancellationToken;
|
||||
Task _workerTask;
|
||||
|
||||
public NewManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
InternalClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
InternalClient.DisconnectedAsync += OnInternalClientDisconnected;
|
||||
|
||||
_logger = logger.WithSource(nameof(NewManagedMqttClient));
|
||||
|
||||
_connectionManager = new MqttConnectionManager(mqttClient, logger);
|
||||
_subscriptionsManager = new ManagedMqttSubscriptionsManager(mqttClient, logger);
|
||||
_applicationMessagesManager = new ApplicationMessagesManager(mqttClient, logger);
|
||||
|
||||
// TODO: Start timer for periodical connection checking.
|
||||
}
|
||||
|
||||
public event Func<EventArgs, Task> ApplicationMessageEnqueueingAsync
|
||||
{
|
||||
add => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.AddHandler(value);
|
||||
remove => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync
|
||||
{
|
||||
add => _applicationMessageProcessedEvent.AddHandler(value);
|
||||
remove => _applicationMessageProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
||||
{
|
||||
add => InternalClient.ApplicationMessageReceivedAsync += value;
|
||||
remove => InternalClient.ApplicationMessageReceivedAsync += value;
|
||||
}
|
||||
|
||||
public event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageDroppedAsync
|
||||
{
|
||||
add => _applicationMessagesManager.ApplicationMessageDroppedEvent.AddHandler(value);
|
||||
remove => _applicationMessagesManager.ApplicationMessageDroppedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
|
||||
{
|
||||
add => _connectionManager.ConnectedEvent.AddHandler(value);
|
||||
remove => _connectionManager.ConnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync
|
||||
{
|
||||
add => _connectionManager.ConnectingFailedEvent.AddHandler(value);
|
||||
remove => _connectionManager.ConnectingFailedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
|
||||
{
|
||||
add => _connectionManager.DisconnectedEvent.AddHandler(value);
|
||||
remove => _connectionManager.DisconnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync
|
||||
{
|
||||
add => _subscriptionsManager.SubscribeProcessedEvent.AddHandler(value);
|
||||
remove => _subscriptionsManager.SubscribeProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync
|
||||
{
|
||||
add => _subscriptionsManager.UnsubscribeProcessedEvent.AddHandler(value);
|
||||
remove => _subscriptionsManager.UnsubscribeProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public IMqttClient InternalClient { get; }
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public bool IsStarted { get; private set; }
|
||||
|
||||
public ManagedMqttClientOptions Options { get; private set; }
|
||||
|
||||
public int EnqueuedApplicationMessagesCount => _applicationMessagesManager.EnqueuedApplicationMessagesCount;
|
||||
|
||||
public Task EnqueueAsync(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
return EnqueueAsync(new ManagedMqttApplicationMessage(Guid.NewGuid().ToString(), applicationMessage));
|
||||
}
|
||||
|
||||
public async Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
await _applicationMessagesManager.Enqueue(applicationMessage).ConfigureAwait(false);
|
||||
_workSignal.Set();
|
||||
}
|
||||
|
||||
public Task PingAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return InternalClient.PingAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task StartAsync(ManagedMqttClientOptions options)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
||||
_workerCancellationToken = new CancellationTokenSource();
|
||||
|
||||
_workerTask = Task.Factory.StartNew(
|
||||
() => WorkerLoop(_workerCancellationToken.Token),
|
||||
_workerCancellationToken.Token,
|
||||
TaskCreationOptions.PreferFairness,
|
||||
TaskScheduler.Default);
|
||||
|
||||
_workSignal.Set();
|
||||
|
||||
return PlatformAbstractionLayer.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task StopAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
// TODO: Send proper Disconnect signal.
|
||||
|
||||
try
|
||||
{
|
||||
await InternalClient.DisconnectAsync(options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while disconnecting.");
|
||||
}
|
||||
|
||||
_workerCancellationToken?.Cancel(false);
|
||||
_workerCancellationToken?.Dispose();
|
||||
|
||||
_workerCancellationToken = null;
|
||||
|
||||
if (_workerTask != null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public Task SubscribeAsync(MqttClientSubscribeOptions options)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_subscriptionsManager.Subscribe(options);
|
||||
_workSignal.Set();
|
||||
|
||||
return PlatformAbstractionLayer.CompletedTask;
|
||||
}
|
||||
|
||||
public Task UnsubscribeAsync(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_subscriptionsManager.Unsubscribe(options);
|
||||
_workSignal.Set();
|
||||
|
||||
return PlatformAbstractionLayer.CompletedTask;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Task OnInternalClientDisconnected(MqttClientDisconnectedEventArgs eventArgs)
|
||||
{
|
||||
_workSignal.Set();
|
||||
return PlatformAbstractionLayer.CompletedTask;
|
||||
}
|
||||
|
||||
async Task UpdateConnectionStatus(MaintainConnectionResult maintainConnectionResult)
|
||||
{
|
||||
var isConnected = maintainConnectionResult != MaintainConnectionResult.NotConnected;
|
||||
|
||||
if (IsConnected.Equals(isConnected))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsConnected = isConnected;
|
||||
|
||||
if (_isConnectedChangedEvent.HasHandlers)
|
||||
{
|
||||
await _isConnectedChangedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task WorkerLoop(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsStarted = true;
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_workSignal.Wait(cancellationToken);
|
||||
_workSignal.Reset();
|
||||
|
||||
var maintainConnectionResult = await _connectionManager.MaintainConnection(Options, cancellationToken).ConfigureAwait(false);
|
||||
await UpdateConnectionStatus(maintainConnectionResult).ConfigureAwait(false);
|
||||
|
||||
if (maintainConnectionResult == MaintainConnectionResult.ConnectedEstablished && Options.ClientOptions.CleanSession)
|
||||
{
|
||||
// Since the session is clean the server will now know any subscription so that
|
||||
// we have to subscribe to all of them again.
|
||||
_subscriptionsManager.ResubscribeAll();
|
||||
}
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
await _subscriptionsManager.Synchronize(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
// TODO: Move into pending messages manager + locking.
|
||||
while (_applicationMessagesManager.TryPeek(out var nextPendingMessage))
|
||||
{
|
||||
Exception processingException = null;
|
||||
try
|
||||
{
|
||||
_logger.Verbose("Publishing managed application message {0}.", nextPendingMessage.Id);
|
||||
|
||||
await InternalClient.PublishAsync(nextPendingMessage.ApplicationMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// The message was delivered successfully. So we can get rid of it.
|
||||
_applicationMessagesManager.Dequeue();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
processingException = exception;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_applicationMessageProcessedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ApplicationMessageProcessedEventArgs(nextPendingMessage, processingException);
|
||||
await _applicationMessageProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
#if !NETSTANDARD1_3
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while maintaining connection.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsStarted = false;
|
||||
|
||||
await UpdateConnectionStatus(MaintainConnectionResult.NotConnected).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// TODO: Disconnected!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttClientOptions
|
||||
{
|
||||
public ManagedMqttClientOptions()
|
||||
{
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptions(MqttClientOptions clientOptions)
|
||||
{
|
||||
ClientOptions = clientOptions;
|
||||
}
|
||||
|
||||
public MqttClientOptions ClientOptions { get; set; }
|
||||
|
||||
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
public TimeSpan ConnectionCheckInterval { get; set; } = TimeSpan.FromSeconds(1);
|
||||
|
||||
public IManagedMqttClientStorage Storage { get; set; }
|
||||
|
||||
public int MaxPendingMessages { get; set; } = int.MaxValue;
|
||||
|
||||
public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropNewMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
|
||||
/// Amazon AWS limits this number to 8. The default is int.MaxValue.
|
||||
/// </summary>
|
||||
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttClientOptionsBuilder
|
||||
{
|
||||
readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
|
||||
MqttClientOptionsBuilder _clientOptionsBuilder;
|
||||
|
||||
public ManagedMqttClientOptionsBuilder WithMaxPendingMessages(int value)
|
||||
{
|
||||
_options.MaxPendingMessages = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptionsBuilder WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy value)
|
||||
{
|
||||
_options.PendingMessagesOverflowStrategy = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptionsBuilder WithAutoReconnectDelay(TimeSpan value)
|
||||
{
|
||||
_options.AutoReconnectDelay = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptionsBuilder WithStorage(IManagedMqttClientStorage value)
|
||||
{
|
||||
_options.Storage = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptionsBuilder WithClientOptions(MqttClientOptions value)
|
||||
{
|
||||
if (_clientOptionsBuilder != null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot use client options builder and client options at the same time.");
|
||||
}
|
||||
|
||||
_options.ClientOptions = value ?? throw new ArgumentNullException(nameof(value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptionsBuilder WithClientOptions(MqttClientOptionsBuilder builder)
|
||||
{
|
||||
if (_options.ClientOptions != null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot use client options builder and client options at the same time.");
|
||||
}
|
||||
|
||||
_clientOptionsBuilder = builder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptionsBuilder WithClientOptions(Action<MqttClientOptionsBuilder> options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (_clientOptionsBuilder == null)
|
||||
{
|
||||
_clientOptionsBuilder = new MqttClientOptionsBuilder();
|
||||
}
|
||||
|
||||
options(_clientOptionsBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptionsBuilder WithMaxTopicFiltersInSubscribeUnsubscribePackets(int value)
|
||||
{
|
||||
_options.MaxTopicFiltersInSubscribeUnsubscribePackets = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptions Build()
|
||||
{
|
||||
if (_clientOptionsBuilder != null)
|
||||
{
|
||||
_options.ClientOptions = _clientOptionsBuilder.Build();
|
||||
}
|
||||
|
||||
if (_options.ClientOptions == null)
|
||||
{
|
||||
throw new InvalidOperationException("The ClientOptions cannot be null.");
|
||||
}
|
||||
|
||||
return _options;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user