mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-22 04:43:04 +08:00
Fix several deadlocks
This commit is contained in:
@@ -12,6 +12,9 @@
|
|||||||
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
|
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
|
||||||
<releaseNotes> * [Client] Added new overloads for the message builder.
|
<releaseNotes> * [Client] Added new overloads for the message builder.
|
||||||
* [Core] Performance optimizations (thanks to @ israellot)
|
* [Core] Performance optimizations (thanks to @ israellot)
|
||||||
|
* [Core] Fixed a memory leak which was caused by not properly stopped async tasks.
|
||||||
|
* [Client] Fixed a deadlock when connecting to a not reachable server.
|
||||||
|
* [Server] Fixed a deadlock when reusing the same _ClientId_ while a will message is used (thanks to @william-wps).
|
||||||
</releaseNotes>
|
</releaseNotes>
|
||||||
<copyright>Copyright Christian Kratky 2016-2018</copyright>
|
<copyright>Copyright Christian Kratky 2016-2018</copyright>
|
||||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
|
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
@@ -12,9 +13,9 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task DisconnectAsync(TimeSpan timeout);
|
Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets);
|
Task SendPacketsAsync(TimeSpan timeout, IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -40,16 +41,16 @@ namespace MQTTnet.Adapter
|
|||||||
Internal.TaskExtensions.TimeoutAfter(ct => _channel.ConnectAsync(ct), timeout, cancellationToken));
|
Internal.TaskExtensions.TimeoutAfter(ct => _channel.ConnectAsync(ct), timeout, cancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DisconnectAsync(TimeSpan timeout)
|
public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
_logger.Verbose<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);
|
_logger.Verbose<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);
|
||||||
|
|
||||||
return ExecuteAndWrapExceptionAsync(() =>
|
return ExecuteAndWrapExceptionAsync(() =>
|
||||||
Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, CancellationToken.None));
|
Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, cancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets)
|
public async Task SendPacketsAsync(TimeSpan timeout, IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
@@ -215,6 +216,7 @@ namespace MQTTnet.Adapter
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
|
|
||||||
_channel?.Dispose();
|
_channel?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ namespace MQTTnet.Client
|
|||||||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
private readonly Stopwatch _sendTracker = new Stopwatch();
|
private readonly Stopwatch _sendTracker = new Stopwatch();
|
||||||
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1);
|
||||||
|
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
||||||
|
|
||||||
private readonly IMqttClientAdapterFactory _adapterFactory;
|
private readonly IMqttClientAdapterFactory _adapterFactory;
|
||||||
private readonly MqttPacketDispatcher _packetDispatcher;
|
|
||||||
private readonly IMqttNetLogger _logger;
|
private readonly IMqttNetLogger _logger;
|
||||||
|
|
||||||
private IMqttClientOptions _options;
|
private IMqttClientOptions _options;
|
||||||
private bool _isReceivingPackets;
|
|
||||||
private CancellationTokenSource _cancellationTokenSource;
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
private Task _packetReceiverTask;
|
private Task _packetReceiverTask;
|
||||||
private Task _keepAliveMessageSenderTask;
|
private Task _keepAliveMessageSenderTask;
|
||||||
@@ -33,8 +33,6 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
|
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
|
||||||
_packetDispatcher = new MqttPacketDispatcher(logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<MqttClientConnectedEventArgs> Connected;
|
public event EventHandler<MqttClientConnectedEventArgs> Connected;
|
||||||
@@ -63,7 +61,7 @@ namespace MQTTnet.Client
|
|||||||
await _adapter.ConnectAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false);
|
await _adapter.ConnectAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
_logger.Verbose<MqttClient>("Connection with server established.");
|
_logger.Verbose<MqttClient>("Connection with server established.");
|
||||||
|
|
||||||
await StartReceivingPacketsAsync().ConfigureAwait(false);
|
StartReceivingPackets(_cancellationTokenSource.Token);
|
||||||
|
|
||||||
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
|
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
|
||||||
_logger.Verbose<MqttClient>("MQTT connection with server established.");
|
_logger.Verbose<MqttClient>("MQTT connection with server established.");
|
||||||
@@ -72,7 +70,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (_options.KeepAlivePeriod != TimeSpan.Zero)
|
if (_options.KeepAlivePeriod != TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
StartSendingKeepAliveMessages();
|
StartSendingKeepAliveMessages(_cancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
IsConnected = true;
|
IsConnected = true;
|
||||||
@@ -271,9 +269,9 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (_adapter != null)
|
if (_adapter != null)
|
||||||
{
|
{
|
||||||
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
|
await _adapter.DisconnectAsync(_options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Verbose<MqttClient>("Disconnected from adapter.");
|
_logger.Verbose<MqttClient>("Disconnected from adapter.");
|
||||||
}
|
}
|
||||||
catch (Exception adapterException)
|
catch (Exception adapterException)
|
||||||
@@ -383,30 +381,45 @@ namespace MQTTnet.Client
|
|||||||
private Task SendAsync(params MqttBasePacket[] packets)
|
private Task SendAsync(params MqttBasePacket[] packets)
|
||||||
{
|
{
|
||||||
_sendTracker.Restart();
|
_sendTracker.Restart();
|
||||||
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packets);
|
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, packets, _cancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
|
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
|
||||||
{
|
{
|
||||||
ushort? identifier = null;
|
_sendTracker.Restart();
|
||||||
if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier)
|
|
||||||
|
ushort identifier = 0;
|
||||||
|
if (requestPacket is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue)
|
||||||
{
|
{
|
||||||
identifier = requestPacketWithIdentifier.PacketIdentifier;
|
identifier = packetWithIdentifier.PacketIdentifier.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout);
|
var packetAwaiter = _packetDispatcher.AddPacketAwaiter<TResponsePacket>(identifier);
|
||||||
await SendAsync(requestPacket).ConfigureAwait(false);
|
try
|
||||||
|
{
|
||||||
|
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, new[] { requestPacket }, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
var respone = await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, _options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
|
return (TResponsePacket)respone;
|
||||||
|
}
|
||||||
|
catch (MqttCommunicationTimedOutException)
|
||||||
|
{
|
||||||
|
_logger.Warning<MqttPacketDispatcher>($"Timeout while waiting for packet of type '{typeof(TResponsePacket).Namespace}'.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_packetDispatcher.RemovePacketAwaiter<TResponsePacket>(identifier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendKeepAliveMessagesAsync()
|
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.Verbose<MqttClient>("Start sending keep alive packets.");
|
_logger.Verbose<MqttClient>("Start sending keep alive packets.");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!_cancellationTokenSource.Token.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
|
var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
|
||||||
if (_options.KeepAliveSendInterval.HasValue)
|
if (_options.KeepAliveSendInterval.HasValue)
|
||||||
@@ -419,7 +432,7 @@ namespace MQTTnet.Client
|
|||||||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
|
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).ConfigureAwait(false);
|
await Task.Delay(keepAliveSendInterval, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
@@ -435,7 +448,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
_logger.Error<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
|
_logger.Error<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
|
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -444,24 +457,22 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReceivePacketsAsync()
|
private async Task ReceivePacketsAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.Verbose<MqttClient>("Start receiving packets.");
|
_logger.Verbose<MqttClient>("Start receiving packets.");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!_cancellationTokenSource.Token.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
_isReceivingPackets = true;
|
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false);
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
|
||||||
if (_cancellationTokenSource.Token.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StartProcessReceivedPacket(packet);
|
StartProcessReceivedPacket(packet, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
@@ -479,6 +490,7 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
|
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
|
||||||
|
_packetDispatcher.Dispatch(exception);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -486,26 +498,19 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartProcessReceivedPacket(MqttBasePacket packet)
|
private void StartProcessReceivedPacket(MqttBasePacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Task.Run(() => ProcessReceivedPacketAsync(packet), _cancellationTokenSource.Token);
|
Task.Run(() => ProcessReceivedPacketAsync(packet), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartReceivingPacketsAsync()
|
private void StartReceivingPackets(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_isReceivingPackets = false;
|
_packetReceiverTask = Task.Run(() => ReceivePacketsAsync(cancellationToken), cancellationToken);
|
||||||
|
|
||||||
_packetReceiverTask = Task.Run(ReceivePacketsAsync, _cancellationTokenSource.Token);
|
|
||||||
|
|
||||||
while (!_isReceivingPackets && !_cancellationTokenSource.Token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
await Task.Delay(TimeSpan.FromMilliseconds(100), _cancellationTokenSource.Token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartSendingKeepAliveMessages()
|
private void StartSendingKeepAliveMessages(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_keepAliveMessageSenderTask = Task.Run(SendKeepAliveMessagesAsync, _cancellationTokenSource.Token);
|
_keepAliveMessageSenderTask = Task.Run(() => SendKeepAliveMessagesAsync(cancellationToken), cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,12 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithKeepAliveSendInterval(TimeSpan value)
|
||||||
|
{
|
||||||
|
_options.KeepAliveSendInterval = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithClientId(string value)
|
public MqttClientOptionsBuilder WithClientId(string value)
|
||||||
{
|
{
|
||||||
_options.ClientId = value;
|
_options.ClientId = value;
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
using MQTTnet.Exceptions;
|
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
namespace MQTTnet.Client
|
||||||
@@ -11,28 +8,12 @@ namespace MQTTnet.Client
|
|||||||
public class MqttPacketDispatcher
|
public class MqttPacketDispatcher
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<Tuple<ushort, Type>, TaskCompletionSource<MqttBasePacket>> _awaiters = new ConcurrentDictionary<Tuple<ushort, Type>, TaskCompletionSource<MqttBasePacket>>();
|
private readonly ConcurrentDictionary<Tuple<ushort, Type>, TaskCompletionSource<MqttBasePacket>> _awaiters = new ConcurrentDictionary<Tuple<ushort, Type>, TaskCompletionSource<MqttBasePacket>>();
|
||||||
private readonly IMqttNetLogger _logger;
|
|
||||||
|
public void Dispatch(Exception exception)
|
||||||
public MqttPacketDispatcher(IMqttNetLogger logger)
|
|
||||||
{
|
{
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
foreach (var awaiter in _awaiters)
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<MqttBasePacket> WaitForPacketAsync(Type responseType, ushort? identifier, TimeSpan timeout)
|
|
||||||
{
|
|
||||||
var packetAwaiter = AddPacketAwaiter(responseType, identifier);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
return await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, timeout, CancellationToken.None).ConfigureAwait(false);
|
awaiter.Value.SetException(exception);
|
||||||
}
|
|
||||||
catch (MqttCommunicationTimedOutException)
|
|
||||||
{
|
|
||||||
_logger.Warning<MqttPacketDispatcher>("Timeout while waiting for packet of type '{0}'.", responseType.Name);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
RemovePacketAwaiter(responseType, identifier);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +44,7 @@ namespace MQTTnet.Client
|
|||||||
_awaiters.Clear();
|
_awaiters.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(Type responseType, ushort? identifier)
|
public TaskCompletionSource<MqttBasePacket> AddPacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
|
||||||
{
|
{
|
||||||
var tcs = new TaskCompletionSource<MqttBasePacket>();
|
var tcs = new TaskCompletionSource<MqttBasePacket>();
|
||||||
|
|
||||||
@@ -71,25 +52,25 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
identifier = 0;
|
identifier = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dictionaryKey = new Tuple<ushort, Type>(identifier ?? 0, responseType);
|
var key = new Tuple<ushort, Type>(identifier ?? 0, typeof(TResponsePacket));
|
||||||
if (!_awaiters.TryAdd(dictionaryKey, tcs))
|
if (!_awaiters.TryAdd(key, tcs))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}.");
|
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{key.Item2.Name}' with identifier {key.Item1}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return tcs;
|
return tcs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemovePacketAwaiter(Type responseType, ushort? identifier)
|
public void RemovePacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
|
||||||
{
|
{
|
||||||
if (!identifier.HasValue)
|
if (!identifier.HasValue)
|
||||||
{
|
{
|
||||||
identifier = 0;
|
identifier = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dictionaryKey = new Tuple<ushort, Type>(identifier ?? 0, responseType);
|
var key = new Tuple<ushort, Type>(identifier ?? 0, typeof(TResponsePacket));
|
||||||
_awaiters.TryRemove(dictionaryKey, out var _);
|
_awaiters.TryRemove(key, out var _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
public sealed class MqttWebSocketChannel : IMqttChannel
|
public sealed class MqttWebSocketChannel : IMqttChannel
|
||||||
{
|
{
|
||||||
|
private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
|
||||||
private readonly MqttClientWebSocketOptions _options;
|
private readonly MqttClientWebSocketOptions _options;
|
||||||
|
|
||||||
private WebSocket _webSocket;
|
private WebSocket _webSocket;
|
||||||
@@ -96,13 +97,26 @@ namespace MQTTnet.Implementations
|
|||||||
return response.Count;
|
return response.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
|
// This lock is required because the client will throw an exception if _SendAsync_ is
|
||||||
|
// called from multiple threads at the same time. But this issue only happens with several
|
||||||
|
// framework versions.
|
||||||
|
await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_sendLock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_sendLock?.Dispose();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_webSocket?.Dispose();
|
_webSocket?.Dispose();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Serializer
|
|||||||
public sealed class MqttPacketSerializer : IMqttPacketSerializer
|
public sealed class MqttPacketSerializer : IMqttPacketSerializer
|
||||||
{
|
{
|
||||||
private static byte[] ProtocolVersionV311Name { get; } = Encoding.UTF8.GetBytes("MQTT");
|
private static byte[] ProtocolVersionV311Name { get; } = Encoding.UTF8.GetBytes("MQTT");
|
||||||
private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIs");
|
private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIsdp");
|
||||||
|
|
||||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||||
|
|
||||||
@@ -203,22 +203,30 @@ namespace MQTTnet.Serializer
|
|||||||
|
|
||||||
private static MqttBasePacket DeserializeConnect(MqttPacketReader reader)
|
private static MqttBasePacket DeserializeConnect(MqttPacketReader reader)
|
||||||
{
|
{
|
||||||
reader.ReadBytes(2); // Skip 2 bytes
|
reader.ReadBytes(2); // Skip 2 bytes for header and remaining length.
|
||||||
|
|
||||||
MqttProtocolVersion protocolVersion;
|
MqttProtocolVersion protocolVersion;
|
||||||
var protocolName = reader.ReadBytes(4);
|
var protocolName = reader.ReadBytes(4);
|
||||||
if (protocolName.SequenceEqual(ProtocolVersionV310Name))
|
|
||||||
{
|
if (protocolName.SequenceEqual(ProtocolVersionV311Name))
|
||||||
reader.ReadBytes(2);
|
|
||||||
protocolVersion = MqttProtocolVersion.V310;
|
|
||||||
}
|
|
||||||
else if (protocolName.SequenceEqual(ProtocolVersionV311Name))
|
|
||||||
{
|
{
|
||||||
protocolVersion = MqttProtocolVersion.V311;
|
protocolVersion = MqttProtocolVersion.V311;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("Protocol name is not supported.");
|
var buffer = new byte[6];
|
||||||
|
Array.Copy(protocolName, buffer, 4);
|
||||||
|
protocolName = reader.ReadBytes(2);
|
||||||
|
Array.Copy(protocolName, 0, buffer, 4, 2);
|
||||||
|
|
||||||
|
if (protocolName.SequenceEqual(ProtocolVersionV310Name))
|
||||||
|
{
|
||||||
|
protocolVersion = MqttProtocolVersion.V310;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("Protocol name is not supported.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.ReadByte(); // Skip protocol level
|
reader.ReadByte(); // Skip protocol level
|
||||||
@@ -323,16 +331,15 @@ namespace MQTTnet.Serializer
|
|||||||
ValidateConnectPacket(packet);
|
ValidateConnectPacket(packet);
|
||||||
|
|
||||||
// Write variable header
|
// Write variable header
|
||||||
writer.Write(0x00, 0x04); // 3.1.2.1 Protocol Name
|
|
||||||
if (ProtocolVersion == MqttProtocolVersion.V311)
|
if (ProtocolVersion == MqttProtocolVersion.V311)
|
||||||
{
|
{
|
||||||
writer.Write(ProtocolVersionV311Name);
|
writer.WriteWithLengthPrefix(ProtocolVersionV311Name);
|
||||||
writer.Write(0x04); // 3.1.2.2 Protocol Level (4)
|
writer.Write(0x04); // 3.1.2.2 Protocol Level 4
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
writer.Write(ProtocolVersionV310Name);
|
writer.WriteWithLengthPrefix(ProtocolVersionV310Name);
|
||||||
writer.Write(0x64, 0x70, 0x03); // Protocol Level (0x03)
|
writer.Write(0x03); // Protocol Level 3
|
||||||
}
|
}
|
||||||
|
|
||||||
var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags
|
var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags
|
||||||
@@ -351,6 +358,11 @@ namespace MQTTnet.Serializer
|
|||||||
connectFlags.Write(false);
|
connectFlags.Write(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (packet.Password != null && packet.Username == null)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22].");
|
||||||
|
}
|
||||||
|
|
||||||
connectFlags.Write(packet.Password != null);
|
connectFlags.Write(packet.Password != null);
|
||||||
connectFlags.Write(packet.Username != null);
|
connectFlags.Write(packet.Username != null);
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,15 @@ using MQTTnet.Serializer;
|
|||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
|
// TODO: Rename to "RegisteredClient"
|
||||||
|
// TODO: Add IsConnected
|
||||||
|
// TODO: Add interface
|
||||||
|
|
||||||
public class ConnectedMqttClient
|
public class ConnectedMqttClient
|
||||||
{
|
{
|
||||||
public string ClientId { get; set; }
|
public string ClientId { get; set; }
|
||||||
|
|
||||||
public MqttProtocolVersion ProtocolVersion { get; set; }
|
public MqttProtocolVersion? ProtocolVersion { get; set; }
|
||||||
|
|
||||||
public TimeSpan LastPacketReceived { get; set; }
|
public TimeSpan LastPacketReceived { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false);
|
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { packet }, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
|
_logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ namespace MQTTnet.Server
|
|||||||
public sealed class MqttClientSession : IDisposable
|
public sealed class MqttClientSession : IDisposable
|
||||||
{
|
{
|
||||||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
private readonly IMqttServerOptions _options;
|
|
||||||
private readonly IMqttNetLogger _logger;
|
|
||||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||||
|
private readonly IMqttNetLogger _logger;
|
||||||
|
private readonly IMqttServerOptions _options;
|
||||||
|
private readonly MqttClientSessionsManager _sessionsManager;
|
||||||
|
|
||||||
private IMqttChannelAdapter _adapter;
|
|
||||||
private CancellationTokenSource _cancellationTokenSource;
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
private MqttApplicationMessage _willMessage;
|
private MqttApplicationMessage _willMessage;
|
||||||
private bool _wasCleanDisconnect;
|
private bool _wasCleanDisconnect;
|
||||||
@@ -28,22 +28,22 @@ namespace MQTTnet.Server
|
|||||||
public MqttClientSession(
|
public MqttClientSession(
|
||||||
string clientId,
|
string clientId,
|
||||||
IMqttServerOptions options,
|
IMqttServerOptions options,
|
||||||
|
MqttClientSessionsManager sessionsManager,
|
||||||
MqttRetainedMessagesManager retainedMessagesManager,
|
MqttRetainedMessagesManager retainedMessagesManager,
|
||||||
IMqttNetLogger logger)
|
IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
|
_sessionsManager = sessionsManager;
|
||||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
|
||||||
ClientId = clientId;
|
ClientId = clientId;
|
||||||
|
|
||||||
KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeoutAsync, _logger);
|
KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeoutAsync, _logger);
|
||||||
SubscriptionsManager = new MqttClientSubscriptionsManager(_options, clientId);
|
SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server);
|
||||||
PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
|
PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<MqttClientSession, MqttApplicationMessage, Task> ApplicationMessageReceivedCallback { get; set; }
|
|
||||||
|
|
||||||
public MqttClientSubscriptionsManager SubscriptionsManager { get; }
|
public MqttClientSubscriptionsManager SubscriptionsManager { get; }
|
||||||
|
|
||||||
public MqttClientPendingMessagesQueue PendingMessagesQueue { get; }
|
public MqttClientPendingMessagesQueue PendingMessagesQueue { get; }
|
||||||
@@ -52,9 +52,9 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public string ClientId { get; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion;
|
public MqttProtocolVersion? ProtocolVersion { get; private set; }
|
||||||
|
|
||||||
public bool IsConnected => _adapter != null;
|
public bool IsConnected { get; private set; }
|
||||||
|
|
||||||
public async Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
|
public async Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
|
||||||
{
|
{
|
||||||
@@ -63,66 +63,73 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cancellationTokenSource = new CancellationTokenSource();
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
_wasCleanDisconnect = false;
|
_wasCleanDisconnect = false;
|
||||||
_willMessage = connectPacket.WillMessage;
|
_willMessage = connectPacket.WillMessage;
|
||||||
_adapter = adapter;
|
|
||||||
_cancellationTokenSource = cancellationTokenSource;
|
|
||||||
|
|
||||||
PendingMessagesQueue.Start(adapter, cancellationTokenSource.Token);
|
IsConnected = true;
|
||||||
KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, cancellationTokenSource.Token);
|
ProtocolVersion = adapter.PacketSerializer.ProtocolVersion;
|
||||||
|
|
||||||
await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false);
|
PendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token);
|
||||||
|
KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token);
|
||||||
|
|
||||||
|
await ReceivePacketsAsync(adapter, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
catch (MqttCommunicationException exception)
|
catch (MqttCommunicationException exception)
|
||||||
{
|
{
|
||||||
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
_logger.Warning<MqttClientSession>(exception,
|
||||||
|
"Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
_logger.Error<MqttClientSession>(exception,
|
||||||
|
"Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ProtocolVersion = null;
|
||||||
|
IsConnected = false;
|
||||||
|
|
||||||
|
_cancellationTokenSource?.Dispose();
|
||||||
|
_cancellationTokenSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _wasCleanDisconnect;
|
return _wasCleanDisconnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StopAsync(bool wasCleanDisconnect = false)
|
public Task StopAsync(bool wasCleanDisconnect = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_cancellationTokenSource == null)
|
if (_cancellationTokenSource == null)
|
||||||
{
|
{
|
||||||
return;
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
_wasCleanDisconnect = wasCleanDisconnect;
|
_wasCleanDisconnect = wasCleanDisconnect;
|
||||||
|
|
||||||
_cancellationTokenSource?.Cancel(false);
|
_cancellationTokenSource?.Cancel(false);
|
||||||
|
|
||||||
PendingMessagesQueue.WaitForCompletion();
|
PendingMessagesQueue.WaitForCompletion();
|
||||||
KeepAliveMonitor.WaitForCompletion();
|
KeepAliveMonitor.WaitForCompletion();
|
||||||
|
|
||||||
_cancellationTokenSource?.Dispose();
|
|
||||||
_cancellationTokenSource = null;
|
|
||||||
|
|
||||||
_adapter = null;
|
|
||||||
|
|
||||||
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
var willMessage = _willMessage;
|
var willMessage = _willMessage;
|
||||||
_willMessage = null; // clear willmessage so it is send just once
|
_willMessage = null; // clear willmessage so it is send just once
|
||||||
|
|
||||||
if (willMessage != null && !wasCleanDisconnect)
|
if (_willMessage != null && !wasCleanDisconnect)
|
||||||
{
|
{
|
||||||
await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false);
|
_sessionsManager.StartDispatchApplicationMessage(this, willMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage)
|
public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage)
|
||||||
@@ -170,8 +177,6 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
ApplicationMessageReceivedCallback = null;
|
|
||||||
|
|
||||||
SubscriptionsManager?.Dispose();
|
SubscriptionsManager?.Dispose();
|
||||||
PendingMessagesQueue?.Dispose();
|
PendingMessagesQueue?.Dispose();
|
||||||
|
|
||||||
@@ -219,7 +224,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (packet is MqttPingReqPacket)
|
if (packet is MqttPingReqPacket)
|
||||||
{
|
{
|
||||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttPingRespPacket() });
|
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { new MqttPingRespPacket() }, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttPubRelPacket pubRelPacket)
|
if (packet is MqttPubRelPacket pubRelPacket)
|
||||||
@@ -234,7 +239,7 @@ namespace MQTTnet.Server
|
|||||||
PacketIdentifier = pubRecPacket.PacketIdentifier
|
PacketIdentifier = pubRecPacket.PacketIdentifier
|
||||||
};
|
};
|
||||||
|
|
||||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { responsePacket });
|
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { responsePacket }, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
|
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
|
||||||
@@ -267,10 +272,19 @@ namespace MQTTnet.Server
|
|||||||
return StopAsync();
|
return StopAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters)
|
||||||
|
{
|
||||||
|
var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters);
|
||||||
|
foreach (var applicationMessage in retainedMessages)
|
||||||
|
{
|
||||||
|
await EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false);
|
var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false);
|
||||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { subscribeResult.ResponsePacket }).ConfigureAwait(false);
|
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { subscribeResult.ResponsePacket }, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (subscribeResult.CloseConnection)
|
if (subscribeResult.CloseConnection)
|
||||||
{
|
{
|
||||||
@@ -283,16 +297,7 @@ namespace MQTTnet.Server
|
|||||||
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
|
var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
|
||||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { unsubscribeResult });
|
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { unsubscribeResult }, cancellationToken);
|
||||||
}
|
|
||||||
|
|
||||||
private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters)
|
|
||||||
{
|
|
||||||
var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters);
|
|
||||||
foreach (var applicationMessage in retainedMessages)
|
|
||||||
{
|
|
||||||
await EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||||
@@ -303,7 +308,8 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||||
{
|
{
|
||||||
return ApplicationMessageReceivedCallback?.Invoke(this, applicationMessage);
|
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
|
||||||
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||||
{
|
{
|
||||||
@@ -322,25 +328,25 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
|
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
|
||||||
|
|
||||||
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
||||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false);
|
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
|
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
|
||||||
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
|
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
|
||||||
|
|
||||||
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
||||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false);
|
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier };
|
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier };
|
||||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response });
|
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,38 +8,34 @@ using MQTTnet.Diagnostics;
|
|||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Serializer;
|
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class MqttClientSessionsManager : IDisposable
|
public sealed class MqttClientSessionsManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
|
private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
|
||||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _sessionsLock = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
private readonly IMqttServerOptions _options;
|
|
||||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||||
|
private readonly IMqttServerOptions _options;
|
||||||
private readonly IMqttNetLogger _logger;
|
private readonly IMqttNetLogger _logger;
|
||||||
|
|
||||||
public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger)
|
public MqttClientSessionsManager(IMqttServerOptions options, MqttServer server, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
|
Server = server ?? throw new ArgumentNullException(nameof(server));
|
||||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action<ConnectedMqttClient> ClientConnectedCallback { get; set; }
|
public MqttServer Server { get; }
|
||||||
public Action<ConnectedMqttClient, bool> ClientDisconnectedCallback { get; set; }
|
|
||||||
public Action<string, TopicFilter> ClientSubscribedTopicCallback { get; set; }
|
|
||||||
public Action<string, string> ClientUnsubscribedTopicCallback { get; set; }
|
|
||||||
public Action<string, MqttApplicationMessage> ApplicationMessageReceivedCallback { get; set; }
|
|
||||||
|
|
||||||
public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
|
public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var clientId = string.Empty;
|
var clientId = string.Empty;
|
||||||
var wasCleanDisconnect = false;
|
var wasCleanDisconnect = false;
|
||||||
MqttClientSession clientSession = null;
|
MqttClientSession clientSession = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken)
|
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken)
|
||||||
@@ -57,30 +53,30 @@ namespace MQTTnet.Server
|
|||||||
var connectReturnCode = ValidateConnection(connectPacket);
|
var connectReturnCode = ValidateConnection(connectPacket);
|
||||||
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
||||||
{
|
{
|
||||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
|
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[]
|
||||||
{
|
{
|
||||||
new MqttConnAckPacket
|
new MqttConnAckPacket
|
||||||
{
|
{
|
||||||
ConnectReturnCode = connectReturnCode
|
ConnectReturnCode = connectReturnCode
|
||||||
}
|
}
|
||||||
}).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false);
|
var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false);
|
||||||
clientSession = result.Session;
|
clientSession = result.Session;
|
||||||
|
|
||||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
|
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[]
|
||||||
{
|
{
|
||||||
new MqttConnAckPacket
|
new MqttConnAckPacket
|
||||||
{
|
{
|
||||||
ConnectReturnCode = connectReturnCode,
|
ConnectReturnCode = connectReturnCode,
|
||||||
IsSessionPresent = result.IsExistingSession
|
IsSessionPresent = result.IsExistingSession
|
||||||
}
|
}
|
||||||
}).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
ClientConnectedCallback?.Invoke(new ConnectedMqttClient
|
Server.OnClientConnected(new ConnectedMqttClient
|
||||||
{
|
{
|
||||||
ClientId = clientId,
|
ClientId = clientId,
|
||||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
|
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
|
||||||
@@ -99,15 +95,15 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
|
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
|
||||||
clientAdapter.Dispose();
|
clientAdapter.Dispose();
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error<MqttClientSessionsManager>(exception, exception.Message);
|
_logger.Error<MqttClientSessionsManager>(exception, exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient
|
Server.OnClientDisconnected(new ConnectedMqttClient
|
||||||
{
|
{
|
||||||
ClientId = clientId,
|
ClientId = clientId,
|
||||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion,
|
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion,
|
||||||
@@ -119,7 +115,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public async Task StopAsync()
|
public async Task StopAsync()
|
||||||
{
|
{
|
||||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var session in _sessions)
|
foreach (var session in _sessions)
|
||||||
@@ -131,19 +127,19 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_semaphore.Release();
|
_sessionsLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
|
public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
|
||||||
{
|
{
|
||||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
|
return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
|
||||||
{
|
{
|
||||||
ClientId = s.Value.ClientId,
|
ClientId = s.Value.ClientId,
|
||||||
ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311,
|
ProtocolVersion = s.Value.ProtocolVersion,
|
||||||
LastPacketReceived = s.Value.KeepAliveMonitor.LastPacketReceived,
|
LastPacketReceived = s.Value.KeepAliveMonitor.LastPacketReceived,
|
||||||
LastNonKeepAlivePacketReceived = s.Value.KeepAliveMonitor.LastNonKeepAlivePacketReceived,
|
LastNonKeepAlivePacketReceived = s.Value.KeepAliveMonitor.LastNonKeepAlivePacketReceived,
|
||||||
PendingApplicationMessages = s.Value.PendingMessagesQueue.Count
|
PendingApplicationMessages = s.Value.PendingMessagesQueue.Count
|
||||||
@@ -151,49 +147,13 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_semaphore.Release();
|
_sessionsLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||||
{
|
{
|
||||||
try
|
Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage));
|
||||||
{
|
|
||||||
var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage);
|
|
||||||
if (interceptorContext.CloseConnection)
|
|
||||||
{
|
|
||||||
await senderClientSession.StopAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationMessage.Retain)
|
|
||||||
{
|
|
||||||
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplicationMessageReceivedCallback?.Invoke(senderClientSession?.ClientId, applicationMessage);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_logger.Error<MqttClientSessionsManager>(exception, "Error while processing application message");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var clientSession in _sessions.Values)
|
|
||||||
{
|
|
||||||
await clientSession.EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_semaphore.Release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
|
public async Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
|
||||||
@@ -201,19 +161,19 @@ namespace MQTTnet.Server
|
|||||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||||
|
|
||||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_sessions.TryGetValue(clientId, out var session))
|
if (!_sessions.TryGetValue(clientId, out var session))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Client session {clientId} is unknown.");
|
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await session.SubscribeAsync(topicFilters);
|
await session.SubscribeAsync(topicFilters).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_semaphore.Release();
|
_sessionsLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,36 +182,25 @@ namespace MQTTnet.Server
|
|||||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||||
|
|
||||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_sessions.TryGetValue(clientId, out var session))
|
if (!_sessions.TryGetValue(clientId, out var session))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Client session {clientId} is unknown.");
|
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await session.UnsubscribeAsync(topicFilters);
|
await session.UnsubscribeAsync(topicFilters).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_semaphore.Release();
|
_sessionsLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
public void Dispose()
|
||||||
{
|
{
|
||||||
var interceptorContext = new MqttApplicationMessageInterceptorContext(
|
_sessionsLock?.Dispose();
|
||||||
senderClientSession?.ClientId,
|
|
||||||
applicationMessage);
|
|
||||||
|
|
||||||
var interceptor = _options.ApplicationMessageInterceptor;
|
|
||||||
if (interceptor == null)
|
|
||||||
{
|
|
||||||
return interceptorContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
interceptor(interceptorContext);
|
|
||||||
return interceptorContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
|
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
|
||||||
@@ -271,9 +220,9 @@ namespace MQTTnet.Server
|
|||||||
return context.ReturnCode;
|
return context.ReturnCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<GetOrCreateClientSessionResult> GetOrCreateClientSessionAsync(MqttConnectPacket connectPacket)
|
private async Task<GetOrCreateClientSessionResult> PrepareClientSessionAsync(MqttConnectPacket connectPacket)
|
||||||
{
|
{
|
||||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
|
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
|
||||||
@@ -300,14 +249,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
isExistingSession = false;
|
isExistingSession = false;
|
||||||
|
|
||||||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, _retainedMessagesManager, _logger)
|
clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _retainedMessagesManager, _logger);
|
||||||
{
|
|
||||||
ApplicationMessageReceivedCallback = DispatchApplicationMessageAsync
|
|
||||||
};
|
|
||||||
|
|
||||||
clientSession.SubscriptionsManager.TopicSubscribedCallback = ClientSubscribedTopicCallback;
|
|
||||||
clientSession.SubscriptionsManager.TopicUnsubscribedCallback = ClientUnsubscribedTopicCallback;
|
|
||||||
|
|
||||||
_sessions[connectPacket.ClientId] = clientSession;
|
_sessions[connectPacket.ClientId] = clientSession;
|
||||||
|
|
||||||
_logger.Verbose<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId);
|
_logger.Verbose<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId);
|
||||||
@@ -317,19 +259,65 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_semaphore.Release();
|
_sessionsLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
private async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||||
{
|
{
|
||||||
ClientConnectedCallback = null;
|
try
|
||||||
ClientDisconnectedCallback = null;
|
{
|
||||||
ClientSubscribedTopicCallback = null;
|
var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage);
|
||||||
ClientUnsubscribedTopicCallback = null;
|
if (interceptorContext.CloseConnection)
|
||||||
ApplicationMessageReceivedCallback = null;
|
{
|
||||||
|
await senderClientSession.StopAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
_semaphore?.Dispose();
|
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applicationMessage.Retain)
|
||||||
|
{
|
||||||
|
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.Error<MqttClientSessionsManager>(exception, "Error while processing application message");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var clientSession in _sessions.Values)
|
||||||
|
{
|
||||||
|
await clientSession.EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_sessionsLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||||
|
{
|
||||||
|
var interceptorContext = new MqttApplicationMessageInterceptorContext(
|
||||||
|
senderClientSession?.ClientId,
|
||||||
|
applicationMessage);
|
||||||
|
|
||||||
|
var interceptor = _options.ApplicationMessageInterceptor;
|
||||||
|
if (interceptor == null)
|
||||||
|
{
|
||||||
|
return interceptorContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
interceptor(interceptorContext);
|
||||||
|
return interceptorContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,17 +13,16 @@ namespace MQTTnet.Server
|
|||||||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
||||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||||
private readonly IMqttServerOptions _options;
|
private readonly IMqttServerOptions _options;
|
||||||
|
private readonly MqttServer _server;
|
||||||
private readonly string _clientId;
|
private readonly string _clientId;
|
||||||
|
|
||||||
public MqttClientSubscriptionsManager(IMqttServerOptions options, string clientId)
|
public MqttClientSubscriptionsManager(string clientId, IMqttServerOptions options, MqttServer server)
|
||||||
{
|
{
|
||||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
|
||||||
_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
|
_server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action<string, TopicFilter> TopicSubscribedCallback { get; set; }
|
|
||||||
public Action<string, string> TopicUnsubscribedCallback { get; set; }
|
|
||||||
|
|
||||||
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttSubscribePacket subscribePacket)
|
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttSubscribePacket subscribePacket)
|
||||||
{
|
{
|
||||||
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
|
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
|
||||||
@@ -61,7 +60,7 @@ namespace MQTTnet.Server
|
|||||||
if (interceptorContext.AcceptSubscription)
|
if (interceptorContext.AcceptSubscription)
|
||||||
{
|
{
|
||||||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
|
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
|
||||||
TopicSubscribedCallback?.Invoke(_clientId, topicFilter);
|
_server.OnClientSubscribedTopic(_clientId, topicFilter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,7 +82,7 @@ namespace MQTTnet.Server
|
|||||||
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
||||||
{
|
{
|
||||||
_subscriptions.Remove(topicFilter);
|
_subscriptions.Remove(topicFilter);
|
||||||
TopicUnsubscribedCallback?.Invoke(_clientId, topicFilter);
|
_server.OnClientUnsubscribedTopic(_clientId, topicFilter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ namespace MQTTnet.Server
|
|||||||
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
|
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
|
public Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||||
{
|
{
|
||||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||||
|
|
||||||
@@ -65,8 +65,10 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
foreach (var applicationMessage in applicationMessages)
|
foreach (var applicationMessage in applicationMessages)
|
||||||
{
|
{
|
||||||
await _clientSessionsManager.DispatchApplicationMessageAsync(null, applicationMessage);
|
_clientSessionsManager.StartDispatchApplicationMessage(null, applicationMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(IMqttServerOptions options)
|
public async Task StartAsync(IMqttServerOptions options)
|
||||||
@@ -80,14 +82,7 @@ namespace MQTTnet.Server
|
|||||||
_retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger);
|
_retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger);
|
||||||
await _retainedMessagesManager.LoadMessagesAsync();
|
await _retainedMessagesManager.LoadMessagesAsync();
|
||||||
|
|
||||||
_clientSessionsManager = new MqttClientSessionsManager(Options, _retainedMessagesManager, _logger)
|
_clientSessionsManager = new MqttClientSessionsManager(Options, this, _retainedMessagesManager, _logger);
|
||||||
{
|
|
||||||
ClientConnectedCallback = OnClientConnected,
|
|
||||||
ClientDisconnectedCallback = OnClientDisconnected,
|
|
||||||
ClientSubscribedTopicCallback = OnClientSubscribedTopic,
|
|
||||||
ClientUnsubscribedTopicCallback = OnClientUnsubscribedTopic,
|
|
||||||
ApplicationMessageReceivedCallback = OnApplicationMessageReceived
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var adapter in _adapters)
|
foreach (var adapter in _adapters)
|
||||||
{
|
{
|
||||||
@@ -132,29 +127,29 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClientConnected(ConnectedMqttClient client)
|
internal void OnClientConnected(ConnectedMqttClient client)
|
||||||
{
|
{
|
||||||
_logger.Info<MqttServer>("Client '{0}': Connected.", client.ClientId);
|
_logger.Info<MqttServer>("Client '{0}': Connected.", client.ClientId);
|
||||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client));
|
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect)
|
internal void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect)
|
||||||
{
|
{
|
||||||
_logger.Info<MqttServer>("Client '{0}': Disconnected (clean={1}).", client.ClientId, wasCleanDisconnect);
|
_logger.Info<MqttServer>("Client '{0}': Disconnected (clean={1}).", client.ClientId, wasCleanDisconnect);
|
||||||
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client, wasCleanDisconnect));
|
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client, wasCleanDisconnect));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter)
|
internal void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter)
|
||||||
{
|
{
|
||||||
ClientSubscribedTopic?.Invoke(this, new MqttClientSubscribedTopicEventArgs(clientId, topicFilter));
|
ClientSubscribedTopic?.Invoke(this, new MqttClientSubscribedTopicEventArgs(clientId, topicFilter));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClientUnsubscribedTopic(string clientId, string topicFilter)
|
internal void OnClientUnsubscribedTopic(string clientId, string topicFilter)
|
||||||
{
|
{
|
||||||
ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter));
|
ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage)
|
internal void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage)
|
||||||
{
|
{
|
||||||
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage));
|
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
|
|||||||
* TLS 1.2 support for client and server (but not UWP servers)
|
* TLS 1.2 support for client and server (but not UWP servers)
|
||||||
* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS)
|
* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS)
|
||||||
* Lightweight (only the low level implementation of MQTT, no overhead)
|
* Lightweight (only the low level implementation of MQTT, no overhead)
|
||||||
* Performance optimized (processing ~50.000 messages / second)*
|
* Performance optimized (processing ~60.000 messages / second)*
|
||||||
* Interfaces included for mocking and testing
|
* Interfaces included for mocking and testing
|
||||||
* Access to internal trace messages
|
* Access to internal trace messages
|
||||||
* Unit tested (~80 tests)
|
* Unit tested (~80 tests)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -25,7 +24,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
CleanSession = true
|
CleanSession = true
|
||||||
};
|
};
|
||||||
|
|
||||||
SerializeAndCompare(p, "EB0ABE1RSXNkcAPCAHsAA1hZWgAEVVNFUgAEUEFTUw==", MqttProtocolVersion.V310);
|
SerializeAndCompare(p, "EB0ABk1RSXNkcAPCAHsAA1hZWgAEVVNFUgAEUEFTUw==", MqttProtocolVersion.V310);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -428,17 +427,6 @@ namespace MQTTnet.Core.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] Join(IEnumerable<ArraySegment<byte>> chunks)
|
|
||||||
{
|
|
||||||
var buffer = new MemoryStream();
|
|
||||||
foreach (var chunk in chunks)
|
|
||||||
{
|
|
||||||
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] Join(params ArraySegment<byte>[] chunks)
|
private static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||||
{
|
{
|
||||||
var buffer = new MemoryStream();
|
var buffer = new MemoryStream();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
@@ -11,7 +13,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MqttSubscriptionsManager_SubscribeSingleSuccess()
|
public void MqttSubscriptionsManager_SubscribeSingleSuccess()
|
||||||
{
|
{
|
||||||
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), "");
|
var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger()));
|
||||||
|
|
||||||
var sp = new MqttSubscribePacket();
|
var sp = new MqttSubscribePacket();
|
||||||
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
|
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
|
||||||
@@ -32,7 +34,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MqttSubscriptionsManager_SubscribeDifferentQoSSuccess()
|
public void MqttSubscriptionsManager_SubscribeDifferentQoSSuccess()
|
||||||
{
|
{
|
||||||
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), "");
|
var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger()));
|
||||||
|
|
||||||
var sp = new MqttSubscribePacket();
|
var sp = new MqttSubscribePacket();
|
||||||
sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtMostOnce));
|
sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtMostOnce));
|
||||||
@@ -53,7 +55,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MqttSubscriptionsManager_SubscribeTwoTimesSuccess()
|
public void MqttSubscriptionsManager_SubscribeTwoTimesSuccess()
|
||||||
{
|
{
|
||||||
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), "");
|
var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger()));
|
||||||
|
|
||||||
var sp = new MqttSubscribePacket();
|
var sp = new MqttSubscribePacket();
|
||||||
sp.TopicFilters.Add(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce));
|
sp.TopicFilters.Add(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce));
|
||||||
@@ -75,7 +77,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MqttSubscriptionsManager_SubscribeSingleNoSuccess()
|
public void MqttSubscriptionsManager_SubscribeSingleNoSuccess()
|
||||||
{
|
{
|
||||||
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), "");
|
var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger()));
|
||||||
|
|
||||||
var sp = new MqttSubscribePacket();
|
var sp = new MqttSubscribePacket();
|
||||||
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
|
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
|
||||||
@@ -94,7 +96,7 @@ namespace MQTTnet.Core.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle()
|
public void MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle()
|
||||||
{
|
{
|
||||||
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), "");
|
var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger()));
|
||||||
|
|
||||||
var sp = new MqttSubscribePacket();
|
var sp = new MqttSubscribePacket();
|
||||||
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
|
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
@@ -25,12 +26,12 @@ namespace MQTTnet.Core.Tests
|
|||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DisconnectAsync(TimeSpan timeout)
|
public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket[] packets)
|
public Task SendPacketsAsync(TimeSpan timeout, IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ThrowIfPartnerIsNull();
|
ThrowIfPartnerIsNull();
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
Console.WriteLine("2 = Start server");
|
Console.WriteLine("2 = Start server");
|
||||||
Console.WriteLine("3 = Start performance test");
|
Console.WriteLine("3 = Start performance test");
|
||||||
Console.WriteLine("4 = Start managed client");
|
Console.WriteLine("4 = Start managed client");
|
||||||
|
Console.WriteLine("5 = Start public broker test");
|
||||||
|
|
||||||
var pressedKey = Console.ReadKey(true);
|
var pressedKey = Console.ReadKey(true);
|
||||||
if (pressedKey.KeyChar == '1')
|
if (pressedKey.KeyChar == '1')
|
||||||
@@ -37,6 +38,10 @@ namespace MQTTnet.TestApp.NetCore
|
|||||||
{
|
{
|
||||||
Task.Run(ManagedClientTest.RunAsync);
|
Task.Run(ManagedClientTest.RunAsync);
|
||||||
}
|
}
|
||||||
|
else if (pressedKey.KeyChar == '5')
|
||||||
|
{
|
||||||
|
Task.Run(PublicBrokerTest.RunAsync);
|
||||||
|
}
|
||||||
|
|
||||||
Thread.Sleep(Timeout.Infinite);
|
Thread.Sleep(Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
using MQTTnet.Client;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MQTTnet.TestApp.NetCore
|
||||||
|
{
|
||||||
|
public static class PublicBrokerTest
|
||||||
|
{
|
||||||
|
public static async Task RunAsync()
|
||||||
|
{
|
||||||
|
//MqttNetGlobalLogger.LogMessagePublished += (s, e) => Console.WriteLine(e.TraceMessage);
|
||||||
|
|
||||||
|
// iot.eclipse.org
|
||||||
|
await ExecuteTestAsync("iot.eclipse.org TCP",
|
||||||
|
new MqttClientOptionsBuilder().WithTcpServer("iot.eclipse.org", 1883).Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync("iot.eclipse.org WS",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("iot.eclipse.org:80/mqtt").Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync("iot.eclipse.org WS TLS",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("iot.eclipse.org:443/mqtt").WithTls().Build());
|
||||||
|
|
||||||
|
// test.mosquitto.org
|
||||||
|
await ExecuteTestAsync("test.mosquitto.org TCP",
|
||||||
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync("test.mosquitto.org TCP TLS",
|
||||||
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithTls().Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync("test.mosquitto.org WS",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync("test.mosquitto.org WS TLS",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").Build());
|
||||||
|
|
||||||
|
// broker.hivemq.com
|
||||||
|
await ExecuteTestAsync("broker.hivemq.com TCP",
|
||||||
|
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync("broker.hivemq.com WS",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build());
|
||||||
|
|
||||||
|
// mqtt.swifitch.cz
|
||||||
|
await ExecuteTestAsync("mqtt.swifitch.cz",
|
||||||
|
new MqttClientOptionsBuilder().WithTcpServer("mqtt.swifitch.cz", 1883).Build());
|
||||||
|
|
||||||
|
// CloudMQTT
|
||||||
|
var configFile = Path.Combine("E:\\CloudMqttTestConfig.json");
|
||||||
|
if (File.Exists(configFile))
|
||||||
|
{
|
||||||
|
var config = JsonConvert.DeserializeObject<MqttConfig>(File.ReadAllText(configFile));
|
||||||
|
|
||||||
|
await ExecuteTestAsync("CloudMQTT TCP",
|
||||||
|
new MqttClientOptionsBuilder().WithTcpServer(config.Server, config.Port).WithCredentials(config.Username, config.Password).Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync("CloudMQTT TCP TLS",
|
||||||
|
new MqttClientOptionsBuilder().WithTcpServer(config.Server, config.SslPort).WithCredentials(config.Username, config.Password).WithTls().Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync("CloudMQTT WS TLS",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer(config.Server + ":" + config.SslWebSocketPort + "/mqtt").WithCredentials(config.Username, config.Password).WithTls().Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("Finished.", ConsoleColor.White);
|
||||||
|
Console.ReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task ExecuteTestAsync(string name, IMqttClientOptions options)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Write("Testing '" + name + "'... ", ConsoleColor.Gray);
|
||||||
|
var factory = new MqttFactory();
|
||||||
|
var client = factory.CreateMqttClient();
|
||||||
|
var topic = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
MqttApplicationMessage receivedMessage = null;
|
||||||
|
client.ApplicationMessageReceived += (s, e) => receivedMessage = e.ApplicationMessage;
|
||||||
|
|
||||||
|
await client.ConnectAsync(options);
|
||||||
|
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
await client.PublishAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
|
||||||
|
|
||||||
|
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
|
||||||
|
{
|
||||||
|
throw new Exception("Message invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.UnsubscribeAsync("test");
|
||||||
|
await client.DisconnectAsync();
|
||||||
|
|
||||||
|
Write("[OK]\n", ConsoleColor.Green);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Write("[FAILED] " + e.Message + "\n", ConsoleColor.Red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Write(string message, ConsoleColor color)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = color;
|
||||||
|
Console.Write(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MqttConfig
|
||||||
|
{
|
||||||
|
public string Server { get; set; }
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
public int SslPort { get; set; }
|
||||||
|
|
||||||
|
public int WebSocketPort { get; set; }
|
||||||
|
|
||||||
|
public int SslWebSocketPort { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user