Compare commits

...
13 Commits
43 changed files with 675 additions and 273 deletions
+3 -3
View File
@@ -2,7 +2,7 @@
<package > <package >
<metadata> <metadata>
<id>MQTTnet.AspNetCore</id> <id>MQTTnet.AspNetCore</id>
<version>2.7.0</version> <version>2.7.3</version>
<authors>Christian Kratky</authors> <authors>Christian Kratky</authors>
<owners>Christian Kratky</owners> <owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> <licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,13 +10,13 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>This is a support library to integrate MQTTnet into AspNetCore.</description> <description>This is a support library to integrate MQTTnet into AspNetCore.</description>
<releaseNotes>* Updated to MQTTnet 2.7.0. <releaseNotes>* Updated to MQTTnet 2.7.3.
</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>
<dependencies> <dependencies>
<group targetFramework="netstandard2.0"> <group targetFramework="netstandard2.0">
<dependency id="MQTTnet" version="2.7.0" /> <dependency id="MQTTnet" version="2.7.3" />
</group> </group>
</dependencies> </dependencies>
</metadata> </metadata>
+9 -10
View File
@@ -2,7 +2,7 @@
<package > <package >
<metadata> <metadata>
<id>MQTTnet</id> <id>MQTTnet</id>
<version>2.7.1</version> <version>2.7.3</version>
<authors>Christian Kratky</authors> <authors>Christian Kratky</authors>
<owners>Christian Kratky</owners> <owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> <licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,15 +10,14 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<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>* [Core] Fixed wrong parsing of ConnAck packet for protocol version 3.1.0. <releaseNotes>* [Core] Add several new extension methods.
* [Core] Log messages are now overriding ToString() and providing a ready to use text representation. * [Client] Fixed an issue in _ManagedMqttClientOptionsBuilder_ when using _WithClientOptions_ and an options builder.
* [Client] Optimized package dispatcher and added several new exceptions. * [Client] Added the "IsStarted" property for the managed client.
* [Client] The _ManagedClient_ now has an event which is fired after a queued application message was processed (including exception). * [Client] Optimized stream buffer for UWP apps.
* [Client] The _ManagedClient_ now supports unsubscribing (thanks to @lerppana) * [Client] Added the _BufferSize_ to the TCP options.
* [Server] Fixed some minor async issues. * [Client] Fixed a race condition which leads to exceptions when reconnecting rapidly.
* [Server] Fixed wrong comparison of the topic and QoS for retained messages. * [Server] Fixed a race condition which leads to exceptions when clients are reconnecting rapidly.
* [Server] Added a property which provides access to the used options (read only). * [Core] Fixed some issues in stream and socket handling.
* [Server] Fixed a null ref expection when using an interceptor and publishing via the server directly.
</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>
@@ -7,7 +7,7 @@ using MQTTnet.Serializer;
namespace MQTTnet.Adapter namespace MQTTnet.Adapter
{ {
public interface IMqttChannelAdapter public interface IMqttChannelAdapter : IDisposable
{ {
IMqttPacketSerializer PacketSerializer { get; } IMqttPacketSerializer PacketSerializer { get; }
@@ -14,11 +14,12 @@ using MQTTnet.Serializer;
namespace MQTTnet.Adapter namespace MQTTnet.Adapter
{ {
public class MqttChannelAdapter : IMqttChannelAdapter public sealed class MqttChannelAdapter : IMqttChannelAdapter
{ {
private const uint ErrorOperationAborted = 0x800703E3; private const uint ErrorOperationAborted = 0x800703E3;
private const int ReadBufferSize = 4096; // TODO: Move buffer size to config private const int ReadBufferSize = 4096; // TODO: Move buffer size to config
private bool _isDisposed;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private readonly IMqttChannel _channel; private readonly IMqttChannel _channel;
@@ -34,6 +35,7 @@ namespace MQTTnet.Adapter
public Task ConnectAsync(TimeSpan timeout) public Task ConnectAsync(TimeSpan timeout)
{ {
ThrowIfDisposed();
_logger.Trace<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout); _logger.Trace<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout);
return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout)); return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout));
@@ -41,6 +43,7 @@ namespace MQTTnet.Adapter
public Task DisconnectAsync(TimeSpan timeout) public Task DisconnectAsync(TimeSpan timeout)
{ {
ThrowIfDisposed();
_logger.Trace<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout); _logger.Trace<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);
return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout)); return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout));
@@ -48,6 +51,8 @@ namespace MQTTnet.Adapter
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets) public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
{ {
ThrowIfDisposed();
return ExecuteAndWrapExceptionAsync(async () => return ExecuteAndWrapExceptionAsync(async () =>
{ {
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -55,6 +60,11 @@ namespace MQTTnet.Adapter
{ {
foreach (var packet in packets) foreach (var packet in packets)
{ {
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (packet == null) if (packet == null)
{ {
continue; continue;
@@ -65,10 +75,20 @@ namespace MQTTnet.Adapter
var chunks = PacketSerializer.Serialize(packet); var chunks = PacketSerializer.Serialize(packet);
foreach (var chunk in chunks) foreach (var chunk in chunks)
{ {
if (cancellationToken.IsCancellationRequested)
{
return;
}
await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false); await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false);
} }
} }
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (timeout > TimeSpan.Zero) if (timeout > TimeSpan.Zero)
{ {
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
@@ -87,6 +107,8 @@ namespace MQTTnet.Adapter
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
{ {
ThrowIfDisposed();
MqttBasePacket packet = null; MqttBasePacket packet = null;
await ExecuteAndWrapExceptionAsync(async () => await ExecuteAndWrapExceptionAsync(async () =>
{ {
@@ -138,7 +160,7 @@ namespace MQTTnet.Adapter
} }
var body = header.BodyLength <= ReadBufferSize ? new MemoryStream(header.BodyLength) : new MemoryStream(); var body = header.BodyLength <= ReadBufferSize ? new MemoryStream(header.BodyLength) : new MemoryStream();
var buffer = new byte[ReadBufferSize]; var buffer = new byte[ReadBufferSize];
while (body.Length < header.BodyLength) while (body.Length < header.BodyLength)
{ {
@@ -149,7 +171,7 @@ namespace MQTTnet.Adapter
} }
var readBytesCount = await stream.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false); var readBytesCount = await stream.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false);
// Check if the client closed the connection before sending the full body. // Check if the client closed the connection before sending the full body.
if (readBytesCount == 0) if (readBytesCount == 0)
{ {
@@ -162,7 +184,7 @@ namespace MQTTnet.Adapter
} }
body.Seek(0L, SeekOrigin.Begin); body.Seek(0L, SeekOrigin.Begin);
return new ReceivedMqttPacket(header, body); return new ReceivedMqttPacket(header, body);
} }
@@ -190,7 +212,7 @@ namespace MQTTnet.Adapter
} }
catch (COMException comException) catch (COMException comException)
{ {
if ((uint) comException.HResult == ErrorOperationAborted) if ((uint)comException.HResult == ErrorOperationAborted)
{ {
throw new OperationCanceledException(); throw new OperationCanceledException();
} }
@@ -214,5 +236,20 @@ namespace MQTTnet.Adapter
throw new MqttCommunicationException(exception); throw new MqttCommunicationException(exception);
} }
} }
public void Dispose()
{
_isDisposed = true;
_semaphore?.Dispose();
_channel?.Dispose();
}
private void ThrowIfDisposed()
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(MqttChannelAdapter));
}
}
} }
} }
@@ -1,17 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Packets;
namespace MQTTnet.Adapter
{
public static class MqttChannelAdapterExtensions
{
public static Task SendPacketsAsync(this IMqttChannelAdapter adapter, TimeSpan timeout, CancellationToken cancellationToken, params MqttBasePacket[] packets)
{
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
return adapter.SendPacketsAsync(timeout, cancellationToken, packets);
}
}
}
@@ -1,16 +1,41 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Protocol;
namespace MQTTnet namespace MQTTnet
{ {
public static class ApplicationMessagePublisherExtensions public static class ApplicationMessagePublisherExtensions
{ {
public static Task PublishAsync(this IApplicationMessagePublisher client, params MqttApplicationMessage[] applicationMessages) public static Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages)
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
return client.PublishAsync(applicationMessages); return publisher.PublishAsync(applicationMessages);
}
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).Build());
}
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).Build());
}
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
} }
} }
} }
@@ -1,9 +1,10 @@
using System.IO; using System;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MQTTnet.Channel namespace MQTTnet.Channel
{ {
public interface IMqttChannel public interface IMqttChannel : IDisposable
{ {
Stream SendStream { get; } Stream SendStream { get; }
Stream ReceiveStream { get; } Stream ReceiveStream { get; }
@@ -13,6 +13,8 @@ namespace MQTTnet.Client
TimeSpan CommunicationTimeout { get; } TimeSpan CommunicationTimeout { get; }
TimeSpan KeepAlivePeriod { get; } TimeSpan KeepAlivePeriod { get; }
TimeSpan? KeepAliveSendInterval { get; set; }
MqttProtocolVersion ProtocolVersion { get; } MqttProtocolVersion ProtocolVersion { get; }
IMqttClientChannelOptions ChannelOptions { get; } IMqttClientChannelOptions ChannelOptions { get; }
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -15,6 +16,8 @@ namespace MQTTnet.Client
public class MqttClient : IMqttClient public class MqttClient : IMqttClient
{ {
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
private readonly Stopwatch _sendTracker = new Stopwatch();
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1);
private readonly IMqttClientAdapterFactory _adapterFactory; private readonly IMqttClientAdapterFactory _adapterFactory;
private readonly MqttPacketDispatcher _packetDispatcher; private readonly MqttPacketDispatcher _packetDispatcher;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
@@ -22,13 +25,15 @@ namespace MQTTnet.Client
private IMqttClientOptions _options; private IMqttClientOptions _options;
private bool _isReceivingPackets; private bool _isReceivingPackets;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private Task _packetReceiverTask;
private Task _keepAliveMessageSenderTask;
private IMqttChannelAdapter _adapter; private IMqttChannelAdapter _adapter;
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger) public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
{ {
_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); _packetDispatcher = new MqttPacketDispatcher(logger);
} }
@@ -53,19 +58,21 @@ namespace MQTTnet.Client
_packetDispatcher.Reset(); _packetDispatcher.Reset();
_adapter = _adapterFactory.CreateClientAdapter(options.ChannelOptions, _logger); _adapter = _adapterFactory.CreateClientAdapter(options.ChannelOptions, _logger);
_logger.Trace<MqttClient>("Trying to connect with server."); _logger.Trace<MqttClient>("Trying to connect with server.");
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
_logger.Trace<MqttClient>("Connection with server established."); _logger.Trace<MqttClient>("Connection with server established.");
await StartReceivingPacketsAsync(_cancellationTokenSource.Token).ConfigureAwait(false); await StartReceivingPacketsAsync().ConfigureAwait(false);
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
_logger.Trace<MqttClient>("MQTT connection with server established."); _logger.Trace<MqttClient>("MQTT connection with server established.");
_sendTracker.Restart();
if (_options.KeepAlivePeriod != TimeSpan.Zero) if (_options.KeepAlivePeriod != TimeSpan.Zero)
{ {
StartSendingKeepAliveMessages(_cancellationTokenSource.Token); StartSendingKeepAliveMessages();
} }
IsConnected = true; IsConnected = true;
@@ -90,7 +97,10 @@ namespace MQTTnet.Client
try try
{ {
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false); if (!_cancellationTokenSource.IsCancellationRequested)
{
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
}
} }
finally finally
{ {
@@ -140,7 +150,7 @@ namespace MQTTnet.Client
ThrowIfNotConnected(); ThrowIfNotConnected();
var publishPackets = applicationMessages.Select(m => m.ToPublishPacket()); var publishPackets = applicationMessages.Select(m => m.ToPublishPacket());
var packetGroups = publishPackets.GroupBy(p => p.QualityOfServiceLevel).OrderBy(g => g.Key); var packetGroups = publishPackets.GroupBy(p => p.QualityOfServiceLevel).OrderBy(g => g.Key);
foreach (var qosGroup in packetGroups) foreach (var qosGroup in packetGroups)
{ {
@@ -149,7 +159,7 @@ namespace MQTTnet.Client
case MqttQualityOfServiceLevel.AtMostOnce: case MqttQualityOfServiceLevel.AtMostOnce:
{ {
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, qosGroup).ConfigureAwait(false); await SendAsync((MqttPublishPacket[])qosGroup.ToArray()).ConfigureAwait(false);
break; break;
} }
case MqttQualityOfServiceLevel.AtLeastOnce: case MqttQualityOfServiceLevel.AtLeastOnce:
@@ -167,8 +177,14 @@ namespace MQTTnet.Client
foreach (var publishPacket in qosGroup) foreach (var publishPacket in qosGroup)
{ {
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false); var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false);
await SendAndReceiveAsync<MqttPubCompPacket>(pubRecPacket.CreateResponse<MqttPubRelPacket>()).ConfigureAwait(false); var pubRelPacket = new MqttPubRelPacket
{
PacketIdentifier = pubRecPacket.PacketIdentifier
};
await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket).ConfigureAwait(false);
} }
break; break;
@@ -184,6 +200,9 @@ namespace MQTTnet.Client
public void Dispose() public void Dispose()
{ {
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_adapter?.Dispose();
} }
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage) private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
@@ -219,23 +238,31 @@ namespace MQTTnet.Client
private async Task DisconnectInternalAsync(Exception exception) private async Task DisconnectInternalAsync(Exception exception)
{ {
await _disconnectLock.WaitAsync();
var clientWasConnected = IsConnected; var clientWasConnected = IsConnected;
IsConnected = false;
var cts = _cancellationTokenSource;
if (cts == null || cts.IsCancellationRequested)
{
return;
}
cts.Cancel(false);
cts.Dispose();
_cancellationTokenSource = null;
try try
{ {
IsConnected = false;
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested)
{
return;
}
_cancellationTokenSource.Cancel(false);
if (_packetReceiverTask != null)
{
Task.WaitAll(_packetReceiverTask);
}
if (_keepAliveMessageSenderTask != null)
{
Task.WaitAll(_keepAliveMessageSenderTask);
}
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
_logger.Info<MqttClient>("Disconnected from adapter."); _logger.Trace<MqttClient>("Disconnected from adapter.");
} }
catch (Exception adapterException) catch (Exception adapterException)
{ {
@@ -243,6 +270,14 @@ namespace MQTTnet.Client
} }
finally finally
{ {
_adapter?.Dispose();
_adapter = null;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_disconnectLock.Release();
_logger.Info<MqttClient>("Disconnected."); _logger.Info<MqttClient>("Disconnected.");
Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception)); Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception));
} }
@@ -253,7 +288,7 @@ namespace MQTTnet.Client
try try
{ {
_logger.Info<MqttClient>("Received <<< {0}", packet); _logger.Info<MqttClient>("Received <<< {0}", packet);
if (packet is MqttPublishPacket publishPacket) if (packet is MqttPublishPacket publishPacket)
{ {
await ProcessReceivedPublishPacketAsync(publishPacket).ConfigureAwait(false); await ProcessReceivedPublishPacketAsync(publishPacket).ConfigureAwait(false);
@@ -301,6 +336,11 @@ namespace MQTTnet.Client
private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket) private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket)
{ {
if (_cancellationTokenSource.IsCancellationRequested)
{
return Task.FromResult(0);
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{ {
FireApplicationMessageReceivedEvent(publishPacket); FireApplicationMessageReceivedEvent(publishPacket);
@@ -325,61 +365,76 @@ namespace MQTTnet.Client
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket) private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
{ {
return SendAsync(pubRelPacket.CreateResponse<MqttPubCompPacket>()); var response = new MqttPubCompPacket
{
PacketIdentifier = pubRelPacket.PacketIdentifier
};
return SendAsync(response);
} }
private Task SendAsync(MqttBasePacket packet) private Task SendAsync(params MqttBasePacket[] packets)
{ {
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packet); _sendTracker.Restart();
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packets);
} }
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
{ {
ushort identifier = 0; ushort? identifier = null;
if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier) if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier)
{ {
identifier = requestPacketWithIdentifier.PacketIdentifier; identifier = requestPacketWithIdentifier.PacketIdentifier;
} }
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout); var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout);
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false); await SendAsync(requestPacket).ConfigureAwait(false);
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false); return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
} }
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken) private async Task SendKeepAliveMessagesAsync()
{ {
_logger.Info<MqttClient>("Start sending keep alive packets."); _logger.Info<MqttClient>("Start sending keep alive packets.");
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!_cancellationTokenSource.Token.IsCancellationRequested)
{ {
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false); var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
await Task.Delay(_options.KeepAlivePeriod, cancellationToken).ConfigureAwait(false); if (_options.KeepAliveSendInterval.HasValue)
{
keepAliveSendInterval = _options.KeepAliveSendInterval.Value;
}
if (_sendTracker.Elapsed > keepAliveSendInterval)
{
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
}
await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
if (cancellationToken.IsCancellationRequested)
{
return;
}
await DisconnectInternalAsync(null).ConfigureAwait(false);
}
catch (MqttCommunicationException exception)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Warning<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets."); if (_cancellationTokenSource.Token.IsCancellationRequested)
{
return;
}
if (exception is MqttCommunicationException)
{
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets.");
}
else
{
_logger.Warning<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
}
await DisconnectInternalAsync(exception).ConfigureAwait(false); await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
finally finally
@@ -388,48 +443,46 @@ namespace MQTTnet.Client
} }
} }
private async Task ReceivePacketsAsync(CancellationToken cancellationToken) private async Task ReceivePacketsAsync()
{ {
_logger.Info<MqttClient>("Start receiving packets."); _logger.Info<MqttClient>("Start receiving packets.");
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!_cancellationTokenSource.Token.IsCancellationRequested)
{ {
_isReceivingPackets = true; _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, cancellationToken); StartProcessReceivedPacket(packet);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
if (cancellationToken.IsCancellationRequested)
{
return;
}
await DisconnectInternalAsync(null).ConfigureAwait(false);
}
catch (MqttCommunicationException exception)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
_logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets.");
await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets."); if (_cancellationTokenSource.IsCancellationRequested)
{
return;
}
if (exception is MqttCommunicationException)
{
_logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets.");
}
else
{
_logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets.");
}
await DisconnectInternalAsync(exception).ConfigureAwait(false); await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
finally finally
@@ -438,38 +491,26 @@ namespace MQTTnet.Client
} }
} }
private void StartProcessReceivedPacket(MqttBasePacket packet, CancellationToken cancellationToken) private void StartProcessReceivedPacket(MqttBasePacket packet)
{ {
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed Task.Run(() => ProcessReceivedPacketAsync(packet), _cancellationTokenSource.Token);
Task.Run(
async () => await ProcessReceivedPacketAsync(packet).ConfigureAwait(false),
cancellationToken).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
} }
private async Task StartReceivingPacketsAsync(CancellationToken cancellationToken) private async Task StartReceivingPacketsAsync()
{ {
_isReceivingPackets = false; _isReceivingPackets = false;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed _packetReceiverTask = Task.Run(ReceivePacketsAsync, _cancellationTokenSource.Token);
Task.Run(
async () => await ReceivePacketsAsync(cancellationToken).ConfigureAwait(false),
cancellationToken).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
while (!_isReceivingPackets && !cancellationToken.IsCancellationRequested) while (!_isReceivingPackets && !_cancellationTokenSource.Token.IsCancellationRequested)
{ {
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken).ConfigureAwait(false); await Task.Delay(TimeSpan.FromMilliseconds(100), _cancellationTokenSource.Token).ConfigureAwait(false);
} }
} }
private void StartSendingKeepAliveMessages(CancellationToken cancellationToken) private void StartSendingKeepAliveMessages()
{ {
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed _keepAliveMessageSenderTask = Task.Run(SendKeepAliveMessagesAsync, _cancellationTokenSource.Token);
Task.Run(
async () => await SendKeepAliveMessagesAsync(cancellationToken).ConfigureAwait(false),
cancellationToken).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
} }
} }
} }
@@ -24,6 +24,14 @@ namespace MQTTnet.Client
return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
} }
public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, string topic)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build());
}
public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters) public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters)
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client == null) throw new ArgumentNullException(nameof(client));
@@ -13,7 +13,9 @@ namespace MQTTnet.Client
public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials(); public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials();
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5); public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
public TimeSpan? KeepAliveSendInterval { get; set; }
public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10); public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
@@ -6,6 +6,8 @@
public int? Port { get; set; } public int? Port { get; set; }
public int BufferSize { get; set; } = 20 * 4096;
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
} }
} }
@@ -9,7 +9,7 @@ namespace MQTTnet.Client
public IDictionary<string, string> RequestHeaders { get; set; } public IDictionary<string, string> RequestHeaders { get; set; }
public ICollection<string> SubProtocols { get; set; } public ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" };
public CookieContainer CookieContainer { get; set; } public CookieContainer CookieContainer { get; set; }
@@ -18,7 +18,7 @@ namespace MQTTnet.Client
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
} }
public async Task<MqttBasePacket> WaitForPacketAsync(Type responseType, ushort identifier, TimeSpan timeout) public async Task<MqttBasePacket> WaitForPacketAsync(Type responseType, ushort? identifier, TimeSpan timeout)
{ {
var packetAwaiter = AddPacketAwaiter(responseType, identifier); var packetAwaiter = AddPacketAwaiter(responseType, identifier);
try try
@@ -44,13 +44,13 @@ namespace MQTTnet.Client
if (_awaiters.TryGetValue(type, out var byId)) if (_awaiters.TryGetValue(type, out var byId))
{ {
ushort identifier = 0; ushort? identifier = 0;
if (packet is IMqttPacketWithIdentifier packetWithIdentifier) if (packet is IMqttPacketWithIdentifier packetWithIdentifier)
{ {
identifier = packetWithIdentifier.PacketIdentifier; identifier = packetWithIdentifier.PacketIdentifier;
} }
if (byId.TryRemove(identifier, out var tcs)) if (byId.TryRemove(identifier.Value, out var tcs))
{ {
tcs.TrySetResult(packet); tcs.TrySetResult(packet);
return; return;
@@ -65,12 +65,17 @@ namespace MQTTnet.Client
_awaiters.Clear(); _awaiters.Clear();
} }
private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(Type responseType, ushort identifier) private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(Type responseType, ushort? identifier)
{ {
var tcs = new TaskCompletionSource<MqttBasePacket>(); var tcs = new TaskCompletionSource<MqttBasePacket>();
if (!identifier.HasValue)
{
identifier = 0;
}
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>()); var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
if (!byId.TryAdd(identifier, tcs)) if (!byId.TryAdd(identifier.Value, 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 '{responseType}' with identifier {identifier}.");
} }
@@ -78,10 +83,15 @@ namespace MQTTnet.Client
return tcs; return tcs;
} }
private void RemovePacketAwaiter(Type responseType, ushort identifier) private void RemovePacketAwaiter(Type responseType, ushort? identifier)
{ {
if (!identifier.HasValue)
{
identifier = 0;
}
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>()); var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
byId.TryRemove(identifier, out var _); byId.TryRemove(identifier.Value, out var _);
} }
} }
} }
@@ -13,14 +13,21 @@ using MQTTnet.Client;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public sealed class MqttTcpChannel : IMqttChannel, IDisposable public sealed class MqttTcpChannel : IMqttChannel
{ {
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
private readonly int _bufferSize = BufferSize;
private readonly MqttClientTcpOptions _options; private readonly MqttClientTcpOptions _options;
private StreamSocket _socket; private StreamSocket _socket;
public MqttTcpChannel(MqttClientTcpOptions options) public MqttTcpChannel(MqttClientTcpOptions options)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_bufferSize = _options.BufferSize;
} }
public MqttTcpChannel(StreamSocket socket) public MqttTcpChannel(StreamSocket socket)
@@ -69,20 +76,56 @@ namespace MQTTnet.Implementations
public void Dispose() public void Dispose()
{ {
SendStream?.Dispose(); try
SendStream = null; {
SendStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
SendStream = null;
}
ReceiveStream?.Dispose(); try
ReceiveStream = null; {
ReceiveStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
ReceiveStream = null;
}
_socket?.Dispose(); try
_socket = null; {
_socket?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
_socket = null;
}
} }
private void CreateStreams() private void CreateStreams()
{ {
SendStream = _socket.OutputStream.AsStreamForWrite(); SendStream = _socket.OutputStream.AsStreamForWrite(_bufferSize);
ReceiveStream = _socket.InputStream.AsStreamForRead(); ReceiveStream = _socket.InputStream.AsStreamForRead(_bufferSize);
} }
private static Certificate LoadCertificate(MqttClientTcpOptions options) private static Certificate LoadCertificate(MqttClientTcpOptions options)
@@ -12,17 +12,20 @@ using MQTTnet.Client;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public sealed class MqttTcpChannel : IMqttChannel, IDisposable public sealed class MqttTcpChannel : IMqttChannel
{ {
private readonly MqttClientTcpOptions _options;
//todo: this can be used with min dependency NetStandard1.6
#if NET452 || NET461 #if NET452 || NET461
// ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user. public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
private readonly int _bufferSize = BufferSize;
#else
private readonly int _bufferSize = 0;
#endif #endif
private readonly MqttClientTcpOptions _options;
private Socket _socket; private Socket _socket;
private SslStream _sslStream; private SslStream _sslStream;
@@ -32,6 +35,7 @@ namespace MQTTnet.Implementations
public MqttTcpChannel(MqttClientTcpOptions options) public MqttTcpChannel(MqttClientTcpOptions options)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_bufferSize = options.BufferSize;
} }
/// <summary> /// <summary>
@@ -43,7 +47,7 @@ namespace MQTTnet.Implementations
_socket = socket ?? throw new ArgumentNullException(nameof(socket)); _socket = socket ?? throw new ArgumentNullException(nameof(socket));
_sslStream = sslStream; _sslStream = sslStream;
CreateStreams(socket, sslStream); CreateStreams();
} }
public Stream SendStream { get; private set; } public Stream SendStream { get; private set; }
@@ -58,7 +62,6 @@ namespace MQTTnet.Implementations
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp); _socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
} }
//todo: else brach can be used with min dependency NET46
#if NET452 || NET461 #if NET452 || NET461
await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false); await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false);
#else #else
@@ -68,10 +71,10 @@ namespace MQTTnet.Implementations
if (_options.TlsOptions.UseTls) if (_options.TlsOptions.UseTls)
{ {
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); _sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
} }
CreateStreams(_socket, _sslStream); CreateStreams();
} }
public Task DisconnectAsync() public Task DisconnectAsync()
@@ -82,11 +85,70 @@ namespace MQTTnet.Implementations
public void Dispose() public void Dispose()
{ {
_socket?.Dispose(); var oneStreamIsUsed = SendStream != null && ReceiveStream != null && ReferenceEquals(SendStream, ReceiveStream);
_socket = null;
_sslStream?.Dispose(); try
_sslStream = null; {
SendStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
SendStream = null;
}
try
{
if (!oneStreamIsUsed)
{
ReceiveStream?.Dispose();
}
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
ReceiveStream = null;
}
try
{
_sslStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
_sslStream = null;
}
try
{
_socket?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
_socket = null;
}
} }
private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
@@ -120,15 +182,15 @@ namespace MQTTnet.Implementations
return _options.TlsOptions.AllowUntrustedCertificates; return _options.TlsOptions.AllowUntrustedCertificates;
} }
private static X509CertificateCollection LoadCertificates(MqttClientTcpOptions options) private X509CertificateCollection LoadCertificates()
{ {
var certificates = new X509CertificateCollection(); var certificates = new X509CertificateCollection();
if (options.TlsOptions.Certificates == null) if (_options.TlsOptions.Certificates == null)
{ {
return certificates; return certificates;
} }
foreach (var certificate in options.TlsOptions.Certificates) foreach (var certificate in _options.TlsOptions.Certificates)
{ {
certificates.Add(new X509Certificate2(certificate)); certificates.Add(new X509Certificate2(certificate));
} }
@@ -136,20 +198,26 @@ namespace MQTTnet.Implementations
return certificates; return certificates;
} }
private void CreateStreams(Socket socket, Stream sslStream) private void CreateStreams()
{ {
var stream = sslStream ?? new NetworkStream(socket); Stream stream;
if (_sslStream != null)
{
stream = _sslStream;
}
else
{
stream = new NetworkStream(_socket, true);
}
//todo: if branch can be used with min dependency NetStandard1.6
#if NET452 || NET461 #if NET452 || NET461
SendStream = new BufferedStream(stream, BufferSize); SendStream = new BufferedStream(stream, _bufferSize);
ReceiveStream = new BufferedStream(stream, BufferSize); ReceiveStream = new BufferedStream(stream, _bufferSize);
#else #else
SendStream = stream; SendStream = stream;
ReceiveStream = stream; ReceiveStream = stream;
#endif #endif
} }
} }
} }
#endif #endif
@@ -96,8 +96,17 @@ namespace MQTTnet.Implementations
public void Dispose() public void Dispose()
{ {
_webSocket?.Dispose(); try
_webSocket = null; {
_webSocket?.Dispose();
}
catch (ObjectDisposedException)
{
}
finally
{
_webSocket = null;
}
} }
} }
} }
@@ -11,9 +11,9 @@ namespace MQTTnet.Implementations
{ {
public class WebSocketStream : Stream public class WebSocketStream : Stream
{ {
private readonly WebSocket _webSocket; private readonly byte[] _chunckBuffer = new byte[MqttWebSocketChannel.BufferSize];
private readonly byte[] _chunkBuffer = new byte[MqttWebSocketChannel.BufferSize];
private readonly Queue<byte> _buffer = new Queue<byte>(MqttWebSocketChannel.BufferSize); private readonly Queue<byte> _buffer = new Queue<byte>(MqttWebSocketChannel.BufferSize);
private readonly WebSocket _webSocket;
public WebSocketStream(WebSocket webSocket) public WebSocketStream(WebSocket webSocket)
{ {
@@ -55,25 +55,28 @@ namespace MQTTnet.Implementations
// Use existing date from buffer. // Use existing date from buffer.
while (count > 0 && _buffer.Any()) while (count > 0 && _buffer.Any())
{ {
buffer[offset++] = _buffer.Dequeue(); buffer[offset] = _buffer.Dequeue();
count--; count--;
bytesRead++; bytesRead++;
offset++;
} }
if (count == 0) if (count == 0)
{ {
return bytesRead; return bytesRead;
} }
// Fetch new data if the buffer is not full.
while (_webSocket.State == WebSocketState.Open) while (_webSocket.State == WebSocketState.Open)
{ {
await FetchChunkAsync(cancellationToken); await FetchChunkAsync(cancellationToken).ConfigureAwait(false);
while (count > 0 && _buffer.Any()) while (count > 0 && _buffer.Any())
{ {
buffer[offset++] = _buffer.Dequeue(); buffer[offset] = _buffer.Dequeue();
count--; count--;
bytesRead++; bytesRead++;
offset++;
} }
if (count == 0) if (count == 0)
@@ -111,19 +114,24 @@ namespace MQTTnet.Implementations
} }
private async Task FetchChunkAsync(CancellationToken cancellationToken) private async Task FetchChunkAsync(CancellationToken cancellationToken)
{ {
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(_chunkBuffer, 0, _chunkBuffer.Length), cancellationToken).ConfigureAwait(false); var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false);
for (var i = 0; i < response.Count; i++)
{
var @byte = _chunkBuffer[i];
_buffer.Enqueue(@byte);
}
if (response.MessageType == WebSocketMessageType.Close) if (response.MessageType == WebSocketMessageType.Close)
{ {
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
} }
else if (response.MessageType == WebSocketMessageType.Binary)
{
for (var i = 0; i < response.Count; i++)
{
_buffer.Enqueue(_chunckBuffer[i]);
}
}
else if (response.MessageType == WebSocketMessageType.Text)
{
throw new MqttProtocolViolationException("WebSocket channel received TEXT message.");
}
} }
} }
} }
@@ -7,6 +7,7 @@ namespace MQTTnet.ManagedClient
{ {
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
{ {
bool IsStarted { get; }
bool IsConnected { get; } bool IsConnected { get; }
event EventHandler<MqttClientConnectedEventArgs> Connected; event EventHandler<MqttClientConnectedEventArgs> Connected;
@@ -40,6 +40,7 @@ namespace MQTTnet.ManagedClient
} }
public bool IsConnected => _mqttClient.IsConnected; public bool IsConnected => _mqttClient.IsConnected;
public bool IsStarted => _connectionCancellationToken != null;
public event EventHandler<MqttClientConnectedEventArgs> Connected; public event EventHandler<MqttClientConnectedEventArgs> Connected;
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected; public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
@@ -21,6 +21,14 @@ namespace MQTTnet.ManagedClient
return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
} }
public static Task SubscribeAsync(this IManagedMqttClient managedClient, string topic)
{
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build());
}
public static Task UnsubscribeAsync(this IManagedMqttClient managedClient, params string[] topicFilters) public static Task UnsubscribeAsync(this IManagedMqttClient managedClient, params string[] topicFilters)
{ {
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient)); if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
@@ -6,6 +6,7 @@ namespace MQTTnet.ManagedClient
public class ManagedMqttClientOptionsBuilder public class ManagedMqttClientOptionsBuilder
{ {
private readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions(); private readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
private MqttClientOptionsBuilder _clientOptionsBuilder;
public ManagedMqttClientOptionsBuilder WithAutoReconnectDelay(TimeSpan value) public ManagedMqttClientOptionsBuilder WithAutoReconnectDelay(TimeSpan value)
{ {
@@ -21,7 +22,24 @@ namespace MQTTnet.ManagedClient
public ManagedMqttClientOptionsBuilder WithClientOptions(IMqttClientOptions value) public ManagedMqttClientOptionsBuilder WithClientOptions(IMqttClientOptions 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)); _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; return this;
} }
@@ -29,15 +47,22 @@ namespace MQTTnet.ManagedClient
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));
var builder = new MqttClientOptionsBuilder(); if (_clientOptionsBuilder == null)
options(builder); {
_options.ClientOptions = builder.Build(); _clientOptionsBuilder = new MqttClientOptionsBuilder();
}
options(_clientOptionsBuilder);
return this; return this;
} }
public ManagedMqttClientOptions Build() public ManagedMqttClientOptions Build()
{ {
if (_clientOptionsBuilder != null)
{
_options.ClientOptions = _clientOptionsBuilder.Build();
}
if (_options.ClientOptions == null) if (_options.ClientOptions == null)
{ {
throw new InvalidOperationException("The ClientOptions cannot be null."); throw new InvalidOperationException("The ClientOptions cannot be null.");
@@ -0,0 +1,25 @@
using System;
using System.Text;
namespace MQTTnet
{
public static class MqttApplicationMessageExtensions
{
public static string ConvertPayloadToString(this MqttApplicationMessage applicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
if (applicationMessage.Payload == null)
{
return null;
}
if (applicationMessage.Payload.Length == 0)
{
return string.Empty;
}
return Encoding.UTF8.GetString(applicationMessage.Payload, 0, applicationMessage.Payload.Length);
}
}
}
@@ -2,6 +2,6 @@
{ {
public interface IMqttPacketWithIdentifier public interface IMqttPacketWithIdentifier
{ {
ushort PacketIdentifier { get; set; } ushort? PacketIdentifier { get; set; }
} }
} }
@@ -2,6 +2,6 @@
{ {
public class MqttBasePublishPacket : MqttBasePacket, IMqttPacketWithIdentifier public class MqttBasePublishPacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort? PacketIdentifier { get; set; }
} }
} }
@@ -1,26 +0,0 @@
using System;
namespace MQTTnet.Packets
{
public static class MqttPacketExtensions
{
public static TResponsePacket CreateResponse<TResponsePacket>(this MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
var responsePacket = Activator.CreateInstance<TResponsePacket>();
if (responsePacket is IMqttPacketWithIdentifier responsePacketWithIdentifier)
{
if (!(packet is IMqttPacketWithIdentifier requestPacketWithIdentifier))
{
throw new InvalidOperationException("Response packet has PacketIdentifier but request packet does not.");
}
responsePacketWithIdentifier.PacketIdentifier = requestPacketWithIdentifier.PacketIdentifier;
}
return responsePacket;
}
}
}
@@ -6,7 +6,7 @@ namespace MQTTnet.Packets
{ {
public sealed class MqttSubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier public sealed class MqttSubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort? PacketIdentifier { get; set; }
public IList<MqttSubscribeReturnCode> SubscribeReturnCodes { get; } = new List<MqttSubscribeReturnCode>(); public IList<MqttSubscribeReturnCode> SubscribeReturnCodes { get; } = new List<MqttSubscribeReturnCode>();
@@ -5,7 +5,7 @@ namespace MQTTnet.Packets
{ {
public sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier public sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort? PacketIdentifier { get; set; }
public IList<TopicFilter> TopicFilters { get; set; } = new List<TopicFilter>(); public IList<TopicFilter> TopicFilters { get; set; } = new List<TopicFilter>();
@@ -2,7 +2,7 @@
{ {
public sealed class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier public sealed class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort? PacketIdentifier { get; set; }
public override string ToString() public override string ToString()
{ {
@@ -4,7 +4,7 @@ namespace MQTTnet.Packets
{ {
public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort? PacketIdentifier { get; set; }
public IList<string> TopicFilters { get; set; } = new List<string>(); public IList<string> TopicFilters { get; set; } = new List<string>();
@@ -24,6 +24,11 @@ namespace MQTTnet.Serializer
public static async Task<MqttPacketHeader> ReadHeaderAsync(Stream stream, CancellationToken cancellationToken) public static async Task<MqttPacketHeader> ReadHeaderAsync(Stream stream, CancellationToken cancellationToken)
{ {
if (cancellationToken.IsCancellationRequested)
{
return null;
}
// Wait for the next package which starts with the header. At this point there will probably // Wait for the next package which starts with the header. At this point there will probably
// some large delay and thus the thread should be put back to the pool (await). So ReadByte() // some large delay and thus the thread should be put back to the pool (await). So ReadByte()
// is not an option here. // is not an option here.
@@ -397,7 +397,12 @@ namespace MQTTnet.Serializer
private static byte Serialize(MqttPubRelPacket packet, MqttPacketWriter writer) private static byte Serialize(MqttPubRelPacket packet, MqttPacketWriter writer)
{ {
writer.Write(packet.PacketIdentifier); if (!packet.PacketIdentifier.HasValue)
{
throw new MqttProtocolViolationException("PubRel packet has no packet identifier.");
}
writer.Write(packet.PacketIdentifier.Value);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02);
} }
@@ -410,7 +415,12 @@ namespace MQTTnet.Serializer
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{ {
writer.Write(packet.PacketIdentifier); if (!packet.PacketIdentifier.HasValue)
{
throw new MqttProtocolViolationException("Publish packet has no packet identifier.");
}
writer.Write(packet.PacketIdentifier.Value);
} }
else else
{ {
@@ -444,21 +454,36 @@ namespace MQTTnet.Serializer
private static byte Serialize(MqttPubAckPacket packet, MqttPacketWriter writer) private static byte Serialize(MqttPubAckPacket packet, MqttPacketWriter writer)
{ {
writer.Write(packet.PacketIdentifier); if (!packet.PacketIdentifier.HasValue)
{
throw new MqttProtocolViolationException("PubAck packet has no packet identifier.");
}
writer.Write(packet.PacketIdentifier.Value);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck);
} }
private static byte Serialize(MqttPubRecPacket packet, MqttPacketWriter writer) private static byte Serialize(MqttPubRecPacket packet, MqttPacketWriter writer)
{ {
writer.Write(packet.PacketIdentifier); if (!packet.PacketIdentifier.HasValue)
{
throw new MqttProtocolViolationException("PubRec packet has no packet identifier.");
}
writer.Write(packet.PacketIdentifier.Value);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec);
} }
private static byte Serialize(MqttPubCompPacket packet, MqttPacketWriter writer) private static byte Serialize(MqttPubCompPacket packet, MqttPacketWriter writer)
{ {
writer.Write(packet.PacketIdentifier); if (!packet.PacketIdentifier.HasValue)
{
throw new MqttProtocolViolationException("PubComp packet has no packet identifier.");
}
writer.Write(packet.PacketIdentifier.Value);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp);
} }
@@ -467,7 +492,12 @@ namespace MQTTnet.Serializer
{ {
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3]."); if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3].");
writer.Write(packet.PacketIdentifier); if (!packet.PacketIdentifier.HasValue)
{
throw new MqttProtocolViolationException("Subscribe packet has no packet identifier.");
}
writer.Write(packet.PacketIdentifier.Value);
if (packet.TopicFilters?.Count > 0) if (packet.TopicFilters?.Count > 0)
{ {
@@ -483,7 +513,12 @@ namespace MQTTnet.Serializer
private static byte Serialize(MqttSubAckPacket packet, MqttPacketWriter writer) private static byte Serialize(MqttSubAckPacket packet, MqttPacketWriter writer)
{ {
writer.Write(packet.PacketIdentifier); if (!packet.PacketIdentifier.HasValue)
{
throw new MqttProtocolViolationException("SubAck packet has no packet identifier.");
}
writer.Write(packet.PacketIdentifier.Value);
if (packet.SubscribeReturnCodes?.Any() == true) if (packet.SubscribeReturnCodes?.Any() == true)
{ {
@@ -500,7 +535,12 @@ namespace MQTTnet.Serializer
{ {
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2]."); if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2].");
writer.Write(packet.PacketIdentifier); if (!packet.PacketIdentifier.HasValue)
{
throw new MqttProtocolViolationException("Unsubscribe packet has no packet identifier.");
}
writer.Write(packet.PacketIdentifier.Value);
if (packet.TopicFilters?.Any() == true) if (packet.TopicFilters?.Any() == true)
{ {
@@ -513,9 +553,14 @@ namespace MQTTnet.Serializer
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
} }
private static byte Serialize(IMqttPacketWithIdentifier packet, BinaryWriter writer) private static byte Serialize(MqttUnsubAckPacket packet, BinaryWriter writer)
{ {
writer.Write(packet.PacketIdentifier); if (!packet.PacketIdentifier.HasValue)
{
throw new MqttProtocolViolationException("UnsubAck packet has no packet identifier.");
}
writer.Write(packet.PacketIdentifier.Value);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
} }
@@ -16,6 +16,8 @@ namespace MQTTnet.Server
private readonly Func<Task> _timeoutCallback; private readonly Func<Task> _timeoutCallback;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private Task _workerTask;
public MqttClientKeepAliveMonitor(string clientId, Func<Task> timeoutCallback, IMqttNetLogger logger) public MqttClientKeepAliveMonitor(string clientId, Func<Task> timeoutCallback, IMqttNetLogger logger)
{ {
_clientId = clientId; _clientId = clientId;
@@ -34,7 +36,15 @@ namespace MQTTnet.Server
return; return;
} }
Task.Run(async () => await RunAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); _workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false), cancellationToken);
}
public void WaitForCompletion()
{
if (_workerTask != null)
{
Task.WaitAll(_workerTask);
}
} }
private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken) private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken)
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
@@ -18,6 +19,8 @@ namespace MQTTnet.Server
private readonly MqttClientSession _clientSession; private readonly MqttClientSession _clientSession;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private Task _workerTask;
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetLogger logger) public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetLogger logger)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
@@ -36,7 +39,15 @@ namespace MQTTnet.Server
return; return;
} }
Task.Run(async () => await SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken).ConfigureAwait(false); _workerTask = Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken);
}
public void WaitForCompletion()
{
if (_workerTask != null)
{
Task.WaitAll(_workerTask);
}
} }
public void Enqueue(MqttBasePacket packet) public void Enqueue(MqttBasePacket packet)
@@ -55,7 +66,7 @@ namespace MQTTnet.Server
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
await SendQueuedPacketAsync(adapter, cancellationToken); await SendNextQueuedPacketAsync(adapter, cancellationToken).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -67,7 +78,7 @@ namespace MQTTnet.Server
} }
} }
private async Task SendQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) private async Task SendNextQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
{ {
MqttBasePacket packet = null; MqttBasePacket packet = null;
try try
@@ -78,7 +89,12 @@ namespace MQTTnet.Server
throw new InvalidOperationException(); // should not happen throw new InvalidOperationException(); // should not happen
} }
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packet).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested)
{
return;
}
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false);
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); _logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
} }
@@ -110,7 +126,10 @@ namespace MQTTnet.Server
} }
} }
await _clientSession.StopAsync(); if (!cancellationToken.IsCancellationRequested)
{
await _clientSession.StopAsync().ConfigureAwait(false);
}
} }
} }
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Internal; using MQTTnet.Internal;
@@ -14,6 +15,7 @@ namespace MQTTnet.Server
{ {
public sealed class MqttClientSession : IDisposable public sealed class MqttClientSession : IDisposable
{ {
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
private readonly IMqttServerOptions _options; private readonly IMqttServerOptions _options;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private readonly MqttRetainedMessagesManager _retainedMessagesManager; private readonly MqttRetainedMessagesManager _retainedMessagesManager;
@@ -94,14 +96,14 @@ namespace MQTTnet.Server
} }
_cancellationTokenSource?.Cancel(false); _cancellationTokenSource?.Cancel(false);
PendingMessagesQueue.WaitForCompletion();
KeepAliveMonitor.WaitForCompletion();
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
_cancellationTokenSource = null; _cancellationTokenSource = null;
if (_adapter != null) _adapter = null;
{
await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
_adapter = null;
}
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId); _logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
} }
@@ -129,6 +131,11 @@ namespace MQTTnet.Server
var publishPacket = applicationMessage.ToPublishPacket(); var publishPacket = applicationMessage.ToPublishPacket();
publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel; publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel;
if (publishPacket.QualityOfServiceLevel > 0)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
}
PendingMessagesQueue.Enqueue(publishPacket); PendingMessagesQueue.Enqueue(publishPacket);
} }
@@ -205,7 +212,7 @@ namespace MQTTnet.Server
if (packet is MqttPingReqPacket) if (packet is MqttPingReqPacket)
{ {
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttPingRespPacket()); return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttPingRespPacket() });
} }
if (packet is MqttPubRelPacket pubRelPacket) if (packet is MqttPubRelPacket pubRelPacket)
@@ -215,7 +222,12 @@ namespace MQTTnet.Server
if (packet is MqttPubRecPacket pubRecPacket) if (packet is MqttPubRecPacket pubRecPacket)
{ {
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, pubRecPacket.CreateResponse<MqttPubRelPacket>()); var responsePacket = new MqttPubRelPacket
{
PacketIdentifier = pubRecPacket.PacketIdentifier
};
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { responsePacket });
} }
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
@@ -246,11 +258,11 @@ namespace MQTTnet.Server
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, subscribeResult.ResponsePacket).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { subscribeResult.ResponsePacket }).ConfigureAwait(false);
if (subscribeResult.CloseConnection) if (subscribeResult.CloseConnection)
{ {
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttDisconnectPacket()).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttDisconnectPacket() }).ConfigureAwait(false);
await StopAsync().ConfigureAwait(false); await StopAsync().ConfigureAwait(false);
} }
@@ -260,7 +272,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, unsubscribeResult); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { unsubscribeResult });
} }
private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters) private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters)
@@ -302,7 +314,7 @@ namespace MQTTnet.Server
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false); await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }; var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).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)
@@ -311,13 +323,13 @@ namespace MQTTnet.Server
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false); await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).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, response); return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response });
} }
} }
} }
@@ -33,16 +33,18 @@ namespace MQTTnet.Server
public Action<string, TopicFilter> ClientSubscribedTopicCallback { get; set; } public Action<string, TopicFilter> ClientSubscribedTopicCallback { get; set; }
public Action<string, string> ClientUnsubscribedTopicCallback { get; set; } public Action<string, string> ClientUnsubscribedTopicCallback { get; set; }
public Action<string, MqttApplicationMessage> ApplicationMessageReceivedCallback { 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;
MqttClientSession clientSession = null; MqttClientSession clientSession = null;
try try
{ {
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false) is MqttConnectPacket connectPacket)) if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken)
.ConfigureAwait(false) is MqttConnectPacket connectPacket))
{ {
throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); throw new MqttProtocolViolationException(
"The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
} }
clientId = connectPacket.ClientId; clientId = connectPacket.ClientId;
@@ -53,9 +55,12 @@ 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 MqttConnAckPacket await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
{ {
ConnectReturnCode = connectReturnCode new MqttConnAckPacket
{
ConnectReturnCode = connectReturnCode
}
}).ConfigureAwait(false); }).ConfigureAwait(false);
return; return;
@@ -64,10 +69,13 @@ namespace MQTTnet.Server
var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false); var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false);
clientSession = result.Session; clientSession = result.Session;
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
{ {
ConnectReturnCode = connectReturnCode, new MqttConnAckPacket
IsSessionPresent = result.IsExistingSession {
ConnectReturnCode = connectReturnCode,
IsSessionPresent = result.IsExistingSession
}
}).ConfigureAwait(false); }).ConfigureAwait(false);
ClientConnectedCallback?.Invoke(new ConnectedMqttClient ClientConnectedCallback?.Invoke(new ConnectedMqttClient
@@ -78,6 +86,9 @@ namespace MQTTnet.Server
await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
} }
catch (OperationCanceledException)
{
}
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClientSessionsManager>(exception, exception.Message); _logger.Error<MqttClientSessionsManager>(exception, exception.Message);
@@ -87,12 +98,13 @@ namespace MQTTnet.Server
try try
{ {
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
clientAdapter.Dispose();
} }
catch (Exception) catch (Exception exception)
{ {
// ignored _logger.Error<MqttClientSessionsManager>(exception, exception.Message);
} }
ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient
{ {
ClientId = clientId, ClientId = clientId,
@@ -308,7 +320,7 @@ namespace MQTTnet.Server
ClientSubscribedTopicCallback = null; ClientSubscribedTopicCallback = null;
ClientUnsubscribedTopicCallback = null; ClientUnsubscribedTopicCallback = null;
ApplicationMessageReceivedCallback = null; ApplicationMessageReceivedCallback = null;
_semaphore?.Dispose(); _semaphore?.Dispose();
} }
} }
@@ -14,7 +14,7 @@ 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 IMqttServerOptions _options; private readonly IMqttServerOptions _options;
private readonly string _clientId; private readonly string _clientId;
public MqttClientSubscriptionsManager(IMqttServerOptions options, string clientId) public MqttClientSubscriptionsManager(IMqttServerOptions options, string clientId)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
@@ -30,7 +30,11 @@ namespace MQTTnet.Server
var result = new MqttClientSubscribeResult var result = new MqttClientSubscribeResult
{ {
ResponsePacket = subscribePacket.CreateResponse<MqttSubAckPacket>(), ResponsePacket = new MqttSubAckPacket
{
PacketIdentifier = subscribePacket.PacketIdentifier
},
CloseConnection = false CloseConnection = false
}; };
@@ -87,7 +91,10 @@ namespace MQTTnet.Server
_semaphore.Release(); _semaphore.Release();
} }
return unsubscribePacket.CreateResponse<MqttUnsubAckPacket>(); return new MqttUnsubAckPacket
{
PacketIdentifier = unsubscribePacket.PacketIdentifier
};
} }
public async Task<CheckSubscriptionsResult> CheckSubscriptionsAsync(MqttApplicationMessage applicationMessage) public async Task<CheckSubscriptionsResult> CheckSubscriptionsAsync(MqttApplicationMessage applicationMessage)
+1 -1
View File
@@ -156,7 +156,7 @@ namespace MQTTnet.Core.Tests
var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build();
await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce)); await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce));
s.PublishAsync(message).Wait(); await s.PublishAsync(message);
await Task.Delay(500); await Task.Delay(500);
} }
finally finally
@@ -17,6 +17,10 @@ namespace MQTTnet.Core.Tests
public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer(); public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer();
public void Dispose()
{
}
public Task ConnectAsync(TimeSpan timeout) public Task ConnectAsync(TimeSpan timeout)
{ {
return Task.FromResult(0); return Task.FromResult(0);
@@ -43,7 +47,17 @@ namespace MQTTnet.Core.Tests
{ {
ThrowIfPartnerIsNull(); ThrowIfPartnerIsNull();
return Task.Run(() => _incomingPackets.Take(), cancellationToken); return Task.Run(() =>
{
try
{
return _incomingPackets.Take(cancellationToken);
}
catch
{
return null;
}
}, cancellationToken);
} }
private void EnqueuePacketInternal(MqttBasePacket packet) private void EnqueuePacketInternal(MqttBasePacket packet)
+1 -1
View File
@@ -89,7 +89,7 @@ namespace MQTTnet.TestApp.NetCore
c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString()); c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString());
} }
} }
catch (Exception e) catch (Exception)
{ {
} }
}; };
@@ -12,7 +12,7 @@
<DefaultLanguage>en-US</DefaultLanguage> <DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier> <TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.16299.0</TargetPlatformVersion> <TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.16299.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.15063.0</TargetPlatformMinVersion> <TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion> <MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
@@ -148,7 +148,7 @@
</PivotItem> </PivotItem>
<PivotItem Header="Log"> <PivotItem Header="Log">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Click="Clear" Width="120">Clear</Button> <Button Click="ClearLog" Width="120">Clear</Button>
</StackPanel> </StackPanel>
</PivotItem> </PivotItem>
</Pivot> </Pivot>
@@ -42,7 +42,8 @@ namespace MQTTnet.TestApp.UniversalWindows
foreach (var traceMessage in _traceMessages) foreach (var traceMessage in _traceMessages)
{ {
logText.AppendFormat( logText.AppendFormat(
"[{0:yyyy-MM-dd HH:mm:ss.fff}] [{1}] [{2}] [{3}] [{4}]{5}", traceMessage.Timestamp, "[{0:yyyy-MM-dd HH:mm:ss.fff}] [{1}] [{2}] [{3}] [{4}]{5}",
traceMessage.Timestamp,
traceMessage.Level, traceMessage.Level,
traceMessage.Source, traceMessage.Source,
traceMessage.ThreadId, traceMessage.ThreadId,
@@ -197,8 +198,13 @@ namespace MQTTnet.TestApp.UniversalWindows
} }
} }
private void Clear(object sender, RoutedEventArgs e) private void ClearLog(object sender, RoutedEventArgs e)
{ {
while (_traceMessages.Count > 0)
{
_traceMessages.TryDequeue(out _);
}
Trace.Text = string.Empty; Trace.Text = string.Empty;
} }