Compare commits

...
11 Commits
Author SHA1 Message Date
Christian 9afff6d8a0 Merge branch 'develop' 2018-04-11 21:22:54 +02:00
Christian ec41efd860 Fix client dead lock and protocol version issues. 2018-04-11 21:22:08 +02:00
Christian c06d84e834 Renamed MqttServerAdapter to MqttTcpServerAdapter 2018-04-06 22:49:41 +02:00
Christian 06a299d212 Update README.md
Remove windows app
2018-04-05 23:40:05 +02:00
Christian f90985a1f9 Merge pull request #214 from JanEggers/Fix175
added tcp adapter
2018-04-05 23:39:10 +02:00
JanEggers 677beddf07 fixed code quality compliance 2018-04-05 16:03:28 +02:00
JanEggers 7154a7b3dc fixed tests 2018-04-05 15:55:08 +02:00
JanEggers 7de3d79d09 added tcp adapter 2018-04-05 14:58:13 +02:00
Christian 54a0265ef9 Fix UnitTests 2018-04-01 20:53:32 +02:00
Christian 8f96acaec4 Fixed a race condition in client and server. 2018-03-31 23:10:14 +02:00
Christian cea984cc2f Fix stream and socket handling. 2018-03-29 22:12:23 +02:00
39 changed files with 630 additions and 269 deletions
+3 -3
View File
@@ -2,7 +2,7 @@
<package > <package >
<metadata> <metadata>
<id>MQTTnet.AspNetCore</id> <id>MQTTnet.AspNetCore</id>
<version>2.7.1</version> <version>2.7.4</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.1. <releaseNotes>* Updated to MQTTnet 2.7.4.
</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.1" /> <dependency id="MQTTnet" version="2.7.4" />
</group> </group>
</dependencies> </dependencies>
</metadata> </metadata>
+9 -7
View File
@@ -2,7 +2,7 @@
<package > <package >
<metadata> <metadata>
<id>MQTTnet</id> <id>MQTTnet</id>
<version>2.7.2</version> <version>2.7.4</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,11 +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>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 the subprotocol "mqtt" as default for web socket based connections. <releaseNotes> * [Client] Fixed a deadlock while the client disconnects.
* [Client] Added a new client setting called "KeepAliveSendInterval". It allows configuring the effective interval for sending ping requests. * [Client] Fixed broken support for protocol version 3.1.0.
* [Client] The client will no longer send ping requests if other packets are sent within the configured interval. * [Server] The _MqttTcpServerAdapter_ is now added to the ASP.NET services.
* [Server] The server now generates a valid packet identifier when disaptching publish packets to clients. * [Server] _MqttServerAdapter_ is renamed to _MqttTcpServerAdapter_ (BREAKING CHANGE!).
* [Core] Add several new extension methods. * [Server] The server no longer sends the will message of a client if the disconnect was clean (via _Disconnect_ packet).
* [Server] The application message interceptor now allows closing the connection.
* [Server] Added a new flag for the _ClientDisconnected_ event which contains a value indicating whether the disconnect was clean (via _Disconnect_ packet).
</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>
@@ -43,7 +45,7 @@
<group targetFramework="net461"> <group targetFramework="net461">
</group> </group>
</dependencies> </dependencies>
</metadata> </metadata>
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Hosting;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Server; using MQTTnet.Server;
using MQTTnet.Implementations;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore
{ {
@@ -20,7 +21,9 @@ namespace MQTTnet.AspNetCore
services.AddSingleton<IMqttServer>(s => s.GetService<MqttHostedServer>()); services.AddSingleton<IMqttServer>(s => s.GetService<MqttHostedServer>());
services.AddSingleton<MqttWebSocketServerAdapter>(); services.AddSingleton<MqttWebSocketServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>()); services.AddSingleton<MqttTcpServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
return services; return services;
} }
@@ -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,20 +35,24 @@ namespace MQTTnet.Adapter
public Task ConnectAsync(TimeSpan timeout) public Task ConnectAsync(TimeSpan timeout)
{ {
_logger.Trace<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout); ThrowIfDisposed();
_logger.Verbose<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout);
return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout)); return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout));
} }
public Task DisconnectAsync(TimeSpan timeout) public Task DisconnectAsync(TimeSpan timeout)
{ {
_logger.Trace<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout); ThrowIfDisposed();
_logger.Verbose<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);
return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout)); return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout));
} }
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,20 +60,35 @@ 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;
} }
_logger.Trace<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout); _logger.Verbose<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout);
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 () =>
{ {
@@ -113,7 +135,7 @@ namespace MQTTnet.Adapter
throw new MqttProtocolViolationException("Received malformed packet."); throw new MqttProtocolViolationException("Received malformed packet.");
} }
_logger.Trace<MqttChannelAdapter>("RX <<< {0}", packet); _logger.Verbose<MqttChannelAdapter>("RX <<< {0}", packet);
} }
finally finally
{ {
@@ -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,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; }
@@ -5,6 +5,6 @@ namespace MQTTnet.Client
{ {
public interface IMqttClientAdapterFactory public interface IMqttClientAdapterFactory
{ {
IMqttChannelAdapter CreateClientAdapter(IMqttClientChannelOptions options, IMqttNetLogger logger); IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetLogger logger);
} }
} }
@@ -13,7 +13,7 @@ namespace MQTTnet.Client
TimeSpan CommunicationTimeout { get; } TimeSpan CommunicationTimeout { get; }
TimeSpan KeepAlivePeriod { get; } TimeSpan KeepAlivePeriod { get; }
TimeSpan? KeepAliveSendInterval { get; set; } TimeSpan? KeepAliveSendInterval { get; }
MqttProtocolVersion ProtocolVersion { get; } MqttProtocolVersion ProtocolVersion { get; }
@@ -17,6 +17,7 @@ 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 IMqttClientAdapterFactory _adapterFactory; private readonly IMqttClientAdapterFactory _adapterFactory;
private readonly MqttPacketDispatcher _packetDispatcher; private readonly MqttPacketDispatcher _packetDispatcher;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
@@ -24,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);
} }
@@ -54,32 +57,34 @@ namespace MQTTnet.Client
_packetIdentifierProvider.Reset(); _packetIdentifierProvider.Reset();
_packetDispatcher.Reset(); _packetDispatcher.Reset();
_adapter = _adapterFactory.CreateClientAdapter(options.ChannelOptions, _logger); _adapter = _adapterFactory.CreateClientAdapter(options, _logger);
_logger.Trace<MqttClient>("Trying to connect with server.");
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
_logger.Trace<MqttClient>("Connection with server established.");
await StartReceivingPacketsAsync(_cancellationTokenSource.Token).ConfigureAwait(false); _logger.Verbose<MqttClient>("Trying to connect with server.");
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
_logger.Verbose<MqttClient>("Connection with server established.");
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.Verbose<MqttClient>("MQTT connection with server established.");
_sendTracker.Restart(); _sendTracker.Restart();
if (_options.KeepAlivePeriod != TimeSpan.Zero) if (_options.KeepAlivePeriod != TimeSpan.Zero)
{ {
StartSendingKeepAliveMessages(_cancellationTokenSource.Token); StartSendingKeepAliveMessages();
} }
IsConnected = true; IsConnected = true;
Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent)); Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent));
_logger.Info<MqttClient>("Connected.");
return new MqttClientConnectResult(connectResponse.IsSessionPresent); return new MqttClientConnectResult(connectResponse.IsSessionPresent);
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClient>(exception, "Error while connecting with server."); _logger.Error<MqttClient>(exception, "Error while connecting with server.");
await DisconnectInternalAsync(exception).ConfigureAwait(false); await DisconnectInternalAsync(null, exception).ConfigureAwait(false);
throw; throw;
} }
@@ -94,11 +99,14 @@ namespace MQTTnet.Client
try try
{ {
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false); if (!_cancellationTokenSource.IsCancellationRequested)
{
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
}
} }
finally finally
{ {
await DisconnectInternalAsync(null).ConfigureAwait(false); await DisconnectInternalAsync(null, null).ConfigureAwait(false);
} }
} }
@@ -144,7 +152,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)
{ {
@@ -153,7 +161,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 SendAsync(qosGroup.ToArray()).ConfigureAwait(false); await SendAsync(qosGroup.Cast<MqttBasePacket>().ToArray()).ConfigureAwait(false);
break; break;
} }
case MqttQualityOfServiceLevel.AtLeastOnce: case MqttQualityOfServiceLevel.AtLeastOnce:
@@ -194,6 +202,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)
@@ -227,25 +238,17 @@ namespace MQTTnet.Client
if (IsConnected) throw new MqttProtocolViolationException(message); if (IsConnected) throw new MqttProtocolViolationException(message);
} }
private async Task DisconnectInternalAsync(Exception exception) private async Task DisconnectInternalAsync(Task sender, Exception exception)
{ {
var clientWasConnected = IsConnected; await _disconnectLock.WaitAsync();
IsConnected = false;
var cts = _cancellationTokenSource;
if (cts == null || cts.IsCancellationRequested)
{
return;
}
cts.Cancel(false);
cts.Dispose();
_cancellationTokenSource = null;
try try
{ {
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested)
_logger.Info<MqttClient>("Disconnected from adapter."); {
return;
}
_cancellationTokenSource.Cancel(false);
} }
catch (Exception adapterException) catch (Exception adapterException)
{ {
@@ -253,6 +256,42 @@ namespace MQTTnet.Client
} }
finally finally
{ {
_disconnectLock.Release();
}
var clientWasConnected = IsConnected;
IsConnected = false;
try
{
if (_packetReceiverTask != null && _packetReceiverTask != sender)
{
_packetReceiverTask.Wait();
}
if (_keepAliveMessageSenderTask != null && _keepAliveMessageSenderTask != sender)
{
_keepAliveMessageSenderTask.Wait();
}
if (_adapter != null)
{
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
}
_logger.Verbose<MqttClient>("Disconnected from adapter.");
}
catch (Exception adapterException)
{
_logger.Warning<MqttClient>(adapterException, "Error while disconnecting from adapter.");
}
finally
{
_adapter?.Dispose();
_adapter = null;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_logger.Info<MqttClient>("Disconnected."); _logger.Info<MqttClient>("Disconnected.");
Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception)); Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception));
} }
@@ -262,8 +301,6 @@ namespace MQTTnet.Client
{ {
try try
{ {
_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);
@@ -311,6 +348,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);
@@ -363,144 +405,112 @@ namespace MQTTnet.Client
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.Verbose<MqttClient>("Start sending keep alive packets.");
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!_cancellationTokenSource.Token.IsCancellationRequested)
{ {
TimeSpan keepAliveSendInterval; var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
if (_options.KeepAliveSendInterval.HasValue) if (_options.KeepAliveSendInterval.HasValue)
{ {
keepAliveSendInterval = _options.KeepAliveSendInterval.Value; keepAliveSendInterval = _options.KeepAliveSendInterval.Value;
} }
else
{
keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
}
if (_sendTracker.Elapsed > keepAliveSendInterval) if (_sendTracker.Elapsed > keepAliveSendInterval)
{ {
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false); await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
} }
await Task.Delay(keepAliveSendInterval, cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
await DisconnectInternalAsync(null).ConfigureAwait(false); await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).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 (exception is OperationCanceledException)
await DisconnectInternalAsync(exception).ConfigureAwait(false); {
}
else if (exception is MqttCommunicationException)
{
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets.");
}
else
{
_logger.Error<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
}
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
} }
finally finally
{ {
_logger.Info<MqttClient>("Stopped sending keep alive packets."); _logger.Verbose<MqttClient>("Stopped sending keep alive packets.");
} }
} }
private async Task ReceivePacketsAsync(CancellationToken cancellationToken) private async Task ReceivePacketsAsync()
{ {
_logger.Info<MqttClient>("Start receiving packets."); _logger.Verbose<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)
{
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 (exception is OperationCanceledException)
await DisconnectInternalAsync(exception).ConfigureAwait(false); {
}
else 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(_packetReceiverTask, exception).ConfigureAwait(false);
} }
finally finally
{ {
_logger.Info<MqttClient>("Stopped receiving packets."); _logger.Verbose<MqttClient>("Stopped receiving packets.");
} }
} }
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
} }
} }
} }
@@ -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();
} }
} }
@@ -6,7 +6,7 @@ namespace MQTTnet.Diagnostics
{ {
event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished; event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished;
void Trace<TSource>(string message, params object[] parameters); void Verbose<TSource>(string message, params object[] parameters);
void Info<TSource>(string message, params object[] parameters); void Info<TSource>(string message, params object[] parameters);
@@ -13,7 +13,7 @@ namespace MQTTnet.Diagnostics
public event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished; public event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished;
public void Trace<TSource>(string message, params object[] parameters) public void Verbose<TSource>(string message, params object[] parameters)
{ {
Publish<TSource>(MqttNetLogLevel.Verbose, null, message, parameters); Publish<TSource>(MqttNetLogLevel.Verbose, null, message, parameters);
} }
@@ -8,18 +8,28 @@ namespace MQTTnet.Implementations
{ {
public class MqttClientAdapterFactory : IMqttClientAdapterFactory public class MqttClientAdapterFactory : IMqttClientAdapterFactory
{ {
public IMqttChannelAdapter CreateClientAdapter(IMqttClientChannelOptions options, IMqttNetLogger logger) public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetLogger logger)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));
switch (options) var serializer = new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion };
switch (options.ChannelOptions)
{ {
case MqttClientTcpOptions tcpOptions: case MqttClientTcpOptions tcpOptions:
return new MqttChannelAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer(), logger); {
return new MqttChannelAdapter(new MqttTcpChannel(tcpOptions), serializer, logger);
}
case MqttClientWebSocketOptions webSocketOptions: case MqttClientWebSocketOptions webSocketOptions:
return new MqttChannelAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer(), logger); {
return new MqttChannelAdapter(new MqttWebSocketChannel(webSocketOptions), serializer, logger);
}
default: default:
throw new NotSupportedException(); {
throw new NotSupportedException();
}
} }
} }
} }
@@ -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,14 +76,56 @@ namespace MQTTnet.Implementations
public void Dispose() public void Dispose()
{ {
_socket?.Dispose(); try
_socket = null; {
SendStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
SendStream = null;
}
try
{
ReceiveStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
ReceiveStream = null;
}
try
{
_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
@@ -9,12 +9,12 @@ using MQTTnet.Server;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public class MqttServerAdapter : IMqttServerAdapter, IDisposable public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable
{ {
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private StreamSocketListener _defaultEndpointSocket; private StreamSocketListener _defaultEndpointSocket;
public MqttServerAdapter(IMqttNetLogger logger) public MqttTcpServerAdapter(IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
} }
@@ -67,7 +67,7 @@ namespace MQTTnet.Implementations
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttServerAdapter>(exception, "Error while accepting connection at default endpoint."); _logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at default endpoint.");
} }
} }
} }
@@ -14,7 +14,7 @@ using MQTTnet.Server;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public class MqttServerAdapter : IMqttServerAdapter, IDisposable public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable
{ {
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
@@ -23,7 +23,7 @@ namespace MQTTnet.Implementations
private Socket _tlsEndpointSocket; private Socket _tlsEndpointSocket;
private X509Certificate2 _tlsCertificate; private X509Certificate2 _tlsCertificate;
public MqttServerAdapter(IMqttNetLogger logger) public MqttTcpServerAdapter(IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
} }
@@ -117,7 +117,7 @@ namespace MQTTnet.Implementations
return; return;
} }
_logger.Error<MqttServerAdapter>(exception, "Error while accepting connection at default endpoint."); _logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at default endpoint.");
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
} }
} }
@@ -152,7 +152,7 @@ namespace MQTTnet.Implementations
return; return;
} }
_logger.Error<MqttServerAdapter>(exception, "Error while accepting connection at TLS endpoint."); _logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at TLS endpoint.");
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
} }
} }
@@ -9,7 +9,7 @@ using MQTTnet.Client;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public sealed class MqttWebSocketChannel : IMqttChannel, IDisposable public sealed class MqttWebSocketChannel : IMqttChannel
{ {
// ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
@@ -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;
}
} }
} }
} }
@@ -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;
@@ -243,7 +244,7 @@ namespace MQTTnet.ManagedClient
} }
finally finally
{ {
_logger.Trace<ManagedMqttClient>("Stopped publishing messages."); _logger.Verbose<ManagedMqttClient>("Stopped publishing messages.");
} }
} }
@@ -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));
@@ -47,7 +47,7 @@ namespace MQTTnet.ManagedClient
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));
if (_clientOptionsBuilder != null) if (_clientOptionsBuilder == null)
{ {
_clientOptionsBuilder = new MqttClientOptionsBuilder(); _clientOptionsBuilder = new MqttClientOptionsBuilder();
} }
@@ -38,7 +38,14 @@ namespace MQTTnet
public IMqttServer CreateMqttServer() public IMqttServer CreateMqttServer()
{ {
var logger = new MqttNetLogger(); var logger = new MqttNetLogger();
return CreateMqttServer(new List<IMqttServerAdapter> { new MqttServerAdapter(logger) }, logger); return CreateMqttServer(logger);
}
public IMqttServer CreateMqttServer(IMqttNetLogger logger)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
return CreateMqttServer(new List<IMqttServerAdapter> { new MqttTcpServerAdapter(logger) }, logger);
} }
public IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) public IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
@@ -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.
@@ -11,5 +11,9 @@
public string ClientId { get; } public string ClientId { get; }
public MqttApplicationMessage ApplicationMessage { get; set; } public MqttApplicationMessage ApplicationMessage { get; set; }
public bool AcceptPublish { get; set; } = true;
public bool CloseConnection { get; set; }
} }
} }
@@ -4,11 +4,14 @@ namespace MQTTnet.Server
{ {
public class MqttClientDisconnectedEventArgs : EventArgs public class MqttClientDisconnectedEventArgs : EventArgs
{ {
public MqttClientDisconnectedEventArgs(ConnectedMqttClient client) public MqttClientDisconnectedEventArgs(ConnectedMqttClient client, bool wasCleanDisconnect)
{ {
Client = client ?? throw new ArgumentNullException(nameof(client)); Client = client ?? throw new ArgumentNullException(nameof(client));
WasCleanDisconnect = wasCleanDisconnect;
} }
public ConnectedMqttClient Client { get; } public ConnectedMqttClient Client { get; }
public bool WasCleanDisconnect { get; }
} }
} }
@@ -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)
@@ -71,7 +81,7 @@ namespace MQTTnet.Server
} }
finally finally
{ {
_logger.Trace<MqttClientSession>("Client {0}: Stopped checking keep alive timeout.", _clientId); _logger.Verbose<MqttClientSession>("Client {0}: Stopped checking keep alive timeout.", _clientId);
} }
} }
@@ -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)
@@ -46,7 +57,7 @@ namespace MQTTnet.Server
_queue.Enqueue(packet); _queue.Enqueue(packet);
_queueWaitSemaphore.Release(); _queueWaitSemaphore.Release();
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _clientSession.ClientId); _logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _clientSession.ClientId);
} }
private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
@@ -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,9 +89,14 @@ namespace MQTTnet.Server
throw new InvalidOperationException(); // should not happen throw new InvalidOperationException(); // should not happen
} }
if (cancellationToken.IsCancellationRequested)
{
return;
}
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false);
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); _logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -110,7 +126,10 @@ namespace MQTTnet.Server
} }
} }
await _clientSession.StopAsync(); if (!cancellationToken.IsCancellationRequested)
{
await _clientSession.StopAsync().ConfigureAwait(false);
}
} }
} }
@@ -23,6 +23,7 @@ namespace MQTTnet.Server
private IMqttChannelAdapter _adapter; private IMqttChannelAdapter _adapter;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private MqttApplicationMessage _willMessage; private MqttApplicationMessage _willMessage;
private bool _wasCleanDisconnect;
public MqttClientSession( public MqttClientSession(
string clientId, string clientId,
@@ -55,7 +56,7 @@ namespace MQTTnet.Server
public bool IsConnected => _adapter != null; public bool IsConnected => _adapter != null;
public async Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) public async Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
{ {
if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket)); if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket));
if (adapter == null) throw new ArgumentNullException(nameof(adapter)); if (adapter == null) throw new ArgumentNullException(nameof(adapter));
@@ -64,6 +65,7 @@ namespace MQTTnet.Server
{ {
var cancellationTokenSource = new CancellationTokenSource(); var cancellationTokenSource = new CancellationTokenSource();
_wasCleanDisconnect = false;
_willMessage = connectPacket.WillMessage; _willMessage = connectPacket.WillMessage;
_adapter = adapter; _adapter = adapter;
_cancellationTokenSource = cancellationTokenSource; _cancellationTokenSource = cancellationTokenSource;
@@ -84,9 +86,11 @@ namespace MQTTnet.Server
{ {
_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);
} }
return _wasCleanDisconnect;
} }
public async Task StopAsync() public async Task StopAsync(bool wasCleanDisconnect = false)
{ {
try try
{ {
@@ -95,24 +99,27 @@ namespace MQTTnet.Server
return; return;
} }
_wasCleanDisconnect = wasCleanDisconnect;
_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);
} }
finally finally
{ {
var willMessage = _willMessage; var willMessage = _willMessage;
if (willMessage != null) _willMessage = null; // clear willmessage so it is send just once
if (willMessage != null && !wasCleanDisconnect)
{ {
_willMessage = null; // clear willmessage so it is send just once
await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false); await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false);
} }
} }
@@ -246,7 +253,12 @@ namespace MQTTnet.Server
return HandleIncomingUnsubscribePacketAsync(adapter, unsubscribePacket, cancellationToken); return HandleIncomingUnsubscribePacketAsync(adapter, unsubscribePacket, cancellationToken);
} }
if (packet is MqttDisconnectPacket || packet is MqttConnectPacket) if (packet is MqttDisconnectPacket)
{
return StopAsync(true);
}
if (packet is MqttConnectPacket)
{ {
return StopAsync(); return StopAsync();
} }
@@ -262,7 +274,6 @@ namespace MQTTnet.Server
if (subscribeResult.CloseConnection) if (subscribeResult.CloseConnection)
{ {
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttDisconnectPacket() }).ConfigureAwait(false);
await StopAsync().ConfigureAwait(false); await StopAsync().ConfigureAwait(false);
} }
@@ -29,20 +29,24 @@ namespace MQTTnet.Server
} }
public Action<ConnectedMqttClient> ClientConnectedCallback { get; set; } public Action<ConnectedMqttClient> ClientConnectedCallback { get; set; }
public Action<ConnectedMqttClient> ClientDisconnectedCallback { get; set; } public Action<ConnectedMqttClient, bool> ClientDisconnectedCallback { get; set; }
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;
var wasCleanDisconnect = false;
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,10 +57,13 @@ 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[] { new MqttConnAckPacket await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
{ {
ConnectReturnCode = connectReturnCode new MqttConnAckPacket
}}).ConfigureAwait(false); {
ConnectReturnCode = connectReturnCode
}
}).ConfigureAwait(false);
return; return;
} }
@@ -64,11 +71,14 @@ 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[] { new MqttConnAckPacket await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
{ {
ConnectReturnCode = connectReturnCode, new MqttConnAckPacket
IsSessionPresent = result.IsExistingSession {
}}).ConfigureAwait(false); ConnectReturnCode = connectReturnCode,
IsSessionPresent = result.IsExistingSession
}
}).ConfigureAwait(false);
ClientConnectedCallback?.Invoke(new ConnectedMqttClient ClientConnectedCallback?.Invoke(new ConnectedMqttClient
{ {
@@ -76,7 +86,10 @@ namespace MQTTnet.Server
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
}); });
await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); wasCleanDisconnect = await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -87,18 +100,20 @@ 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,
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion, ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion,
PendingApplicationMessages = clientSession?.PendingMessagesQueue.Count ?? 0 PendingApplicationMessages = clientSession?.PendingMessagesQueue.Count ?? 0
}); },
wasCleanDisconnect);
} }
} }
@@ -144,8 +159,13 @@ namespace MQTTnet.Server
{ {
try try
{ {
applicationMessage = InterceptApplicationMessage(senderClientSession, applicationMessage); var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage);
if (applicationMessage == null) if (interceptorContext.CloseConnection)
{
await senderClientSession.StopAsync().ConfigureAwait(false);
}
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
{ {
return; return;
} }
@@ -218,20 +238,20 @@ namespace MQTTnet.Server
} }
} }
private MqttApplicationMessage InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
{ {
var interceptor = _options.ApplicationMessageInterceptor;
if (interceptor == null)
{
return applicationMessage;
}
var interceptorContext = new MqttApplicationMessageInterceptorContext( var interceptorContext = new MqttApplicationMessageInterceptorContext(
senderClientSession?.ClientId, senderClientSession?.ClientId,
applicationMessage); applicationMessage);
var interceptor = _options.ApplicationMessageInterceptor;
if (interceptor == null)
{
return interceptorContext;
}
interceptor(interceptorContext); interceptor(interceptorContext);
return interceptorContext.ApplicationMessage; return interceptorContext;
} }
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
@@ -267,11 +287,11 @@ namespace MQTTnet.Server
clientSession.Dispose(); clientSession.Dispose();
clientSession = null; clientSession = null;
_logger.Trace<MqttClientSessionsManager>("Stopped existing session of client '{0}'.", connectPacket.ClientId); _logger.Verbose<MqttClientSessionsManager>("Stopped existing session of client '{0}'.", connectPacket.ClientId);
} }
else else
{ {
_logger.Trace<MqttClientSessionsManager>("Reusing existing session of client '{0}'.", connectPacket.ClientId); _logger.Verbose<MqttClientSessionsManager>("Reusing existing session of client '{0}'.", connectPacket.ClientId);
} }
} }
@@ -290,7 +310,7 @@ namespace MQTTnet.Server
_sessions[connectPacket.ClientId] = clientSession; _sessions[connectPacket.ClientId] = clientSession;
_logger.Trace<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId); _logger.Verbose<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId);
} }
return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession }; return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
@@ -308,7 +328,7 @@ namespace MQTTnet.Server
ClientSubscribedTopicCallback = null; ClientSubscribedTopicCallback = null;
ClientUnsubscribedTopicCallback = null; ClientUnsubscribedTopicCallback = null;
ApplicationMessageReceivedCallback = null; ApplicationMessageReceivedCallback = null;
_semaphore?.Dispose(); _semaphore?.Dispose();
} }
} }
@@ -132,7 +132,7 @@ namespace MQTTnet.Server
if (!saveIsRequired) if (!saveIsRequired)
{ {
_logger.Trace<MqttRetainedMessagesManager>("Skipped saving retained messages because no changes were detected."); _logger.Verbose<MqttRetainedMessagesManager>("Skipped saving retained messages because no changes were detected.");
} }
if (saveIsRequired && _options.Storage != null) if (saveIsRequired && _options.Storage != null)
@@ -138,10 +138,10 @@ namespace MQTTnet.Server
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client)); ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client));
} }
private void OnClientDisconnected(ConnectedMqttClient client) private void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect)
{ {
_logger.Info<MqttServer>("Client '{0}': Disconnected.", client.ClientId); _logger.Info<MqttServer>("Client '{0}': Disconnected (clean={1}).", client.ClientId, wasCleanDisconnect);
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client)); ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client, wasCleanDisconnect));
} }
private void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter) private void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter)
@@ -162,7 +162,7 @@ namespace MQTTnet.Server
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs)
{ {
eventArgs.SessionTask = Task.Run( eventArgs.SessionTask = Task.Run(
async () => await _clientSessionsManager.RunSessionAsync(eventArgs.Client, _cancellationTokenSource.Token).ConfigureAwait(false), () => _clientSessionsManager.RunSessionAsync(eventArgs.Client, _cancellationTokenSource.Token),
_cancellationTokenSource.Token); _cancellationTokenSource.Token);
} }
} }
+1 -1
View File
@@ -78,7 +78,7 @@ If you want to contribute to this project just create a pull request. But only p
This library is used in the following projects: This library is used in the following projects:
* MQTT Client Rx (Wrapper for Reactive Extensions, <https://github.com/1iveowl/MQTTClient.rx>) * MQTT Client Rx (Wrapper for Reactive Extensions, <https://github.com/1iveowl/MQTTClient.rx>)
* MQTT Tester (MQTT client test app for [Android](https://play.google.com/store/apps/details?id=com.liveowl.mqtttester), [iOS](https://itunes.apple.com/us/app/mqtt-tester/id1278621826?mt=8), and [Windows 10](https://www.microsoft.com/en-us/store/p/mqtt-tester/9wzdncrd272c)) * MQTT Tester (MQTT client test app for [Android](https://play.google.com/store/apps/details?id=com.liveowl.mqtttester) and [iOS](https://itunes.apple.com/us/app/mqtt-tester/id1278621826?mt=8))
* Wirehome (Open Source Home Automation system for .NET, <https://github.com/chkr1011/Wirehome>) * Wirehome (Open Source Home Automation system for .NET, <https://github.com/chkr1011/Wirehome>)
+9 -12
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
@@ -209,12 +209,9 @@ namespace MQTTnet.Core.Tests
await s.StartAsync(new MqttServerOptions()); await s.StartAsync(new MqttServerOptions());
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient(s, "c1");
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.DisconnectAsync(); await c1.DisconnectAsync();
await Task.Delay(TimeSpan.FromSeconds(2));
// TODO: Find another way to wait for the retained components.
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); var c2 = await serverAdapter.ConnectTestClient(s, "c2");
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build());
@@ -244,7 +241,7 @@ namespace MQTTnet.Core.Tests
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build()); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build());
await c1.DisconnectAsync(); await c1.DisconnectAsync();
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); var c2 = await serverAdapter.ConnectTestClient(s, "c2");
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
await c2.SubscribeAsync(new TopicFilter("retained", MqttQualityOfServiceLevel.AtMostOnce)); await c2.SubscribeAsync(new TopicFilter("retained", MqttQualityOfServiceLevel.AtMostOnce));
@@ -274,7 +271,8 @@ namespace MQTTnet.Core.Tests
await s.StartAsync(options); await s.StartAsync(options);
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient(s, "c1");
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.DisconnectAsync(); await c1.DisconnectAsync();
} }
finally finally
@@ -282,8 +280,7 @@ namespace MQTTnet.Core.Tests
await s.StopAsync(); await s.StopAsync();
} }
await Task.Delay(TimeSpan.FromSeconds(2)); Assert.AreEqual(1, storage.Messages.Count);
// TODO: Find another way to wait for the retained components.
s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger());
@@ -385,17 +382,17 @@ namespace MQTTnet.Core.Tests
private class TestStorage : IMqttServerStorage private class TestStorage : IMqttServerStorage
{ {
private IList<MqttApplicationMessage> _messages = new List<MqttApplicationMessage>(); public IList<MqttApplicationMessage> Messages = new List<MqttApplicationMessage>();
public Task SaveRetainedMessagesAsync(IList<MqttApplicationMessage> messages) public Task SaveRetainedMessagesAsync(IList<MqttApplicationMessage> messages)
{ {
_messages = messages; Messages = messages;
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task<IList<MqttApplicationMessage>> LoadRetainedMessagesAsync() public Task<IList<MqttApplicationMessage>> LoadRetainedMessagesAsync()
{ {
return Task.FromResult(_messages); return Task.FromResult(Messages);
} }
} }
@@ -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)
@@ -13,7 +13,7 @@ namespace MQTTnet.Core.Tests
_adapter = adapter; _adapter = adapter;
} }
public IMqttChannelAdapter CreateClientAdapter(IMqttClientChannelOptions options, IMqttNetLogger logger) public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetLogger logger)
{ {
return _adapter; return _adapter;
} }
@@ -0,0 +1,38 @@
using MQTTnet.Client;
using MQTTnet.Server;
using System;
using System.Threading.Tasks;
namespace MQTTnet.Core.Tests
{
public static class TestServerExtensions
{
/// <summary>
/// publishes a message with a client and waits in the server until a message with the same topic is received
/// </summary>
/// <returns></returns>
public static async Task PublishAndWaitForAsync(this IMqttClient client, IMqttServer server, MqttApplicationMessage message)
{
var tcs = new TaskCompletionSource<object>();
EventHandler<MqttApplicationMessageReceivedEventArgs> handler = (sender, args) =>
{
if (args.ApplicationMessage.Topic == message.Topic)
{
tcs.SetResult(true);
}
};
server.ApplicationMessageReceived += handler;
try
{
await client.PublishAsync(message).ConfigureAwait(false);
await tcs.Task.ConfigureAwait(false);
}
finally
{
server.ApplicationMessageReceived -= handler;
}
}
}
}
+26 -21
View File
@@ -3,7 +3,6 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
using Newtonsoft.Json.Linq;
namespace MQTTnet.TestApp.NetCore namespace MQTTnet.TestApp.NetCore
{ {
@@ -38,6 +37,12 @@ namespace MQTTnet.TestApp.NetCore
// based payload with the timestamp is a suitable use case. // based payload with the timestamp is a suitable use case.
context.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")); context.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"));
} }
if (context.ApplicationMessage.Topic == "not_allowed_topic")
{
context.AcceptPublish = false;
context.CloseConnection = true;
}
}, },
SubscriptionInterceptor = context => SubscriptionInterceptor = context =>
{ {
@@ -72,27 +77,27 @@ namespace MQTTnet.TestApp.NetCore
ConsoleColor.Magenta); ConsoleColor.Magenta);
}; };
options.ApplicationMessageInterceptor = c => //options.ApplicationMessageInterceptor = c =>
{ //{
if (c.ApplicationMessage.Payload == null || c.ApplicationMessage.Payload.Length == 0) // if (c.ApplicationMessage.Payload == null || c.ApplicationMessage.Payload.Length == 0)
{ // {
return; // return;
} // }
try // try
{ // {
var content = JObject.Parse(Encoding.UTF8.GetString(c.ApplicationMessage.Payload)); // var content = JObject.Parse(Encoding.UTF8.GetString(c.ApplicationMessage.Payload));
var timestampProperty = content.Property("timestamp"); // var timestampProperty = content.Property("timestamp");
if (timestampProperty != null && timestampProperty.Value.Type == JTokenType.Null) // if (timestampProperty != null && timestampProperty.Value.Type == JTokenType.Null)
{ // {
timestampProperty.Value = DateTime.Now.ToString("O"); // timestampProperty.Value = DateTime.Now.ToString("O");
c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString()); // c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString());
} // }
} // }
catch (Exception e) // catch (Exception)
{ // {
} // }
}; //};
mqttServer.ClientDisconnected += (s, e) => mqttServer.ClientDisconnected += (s, e) =>
{ {
@@ -13,6 +13,8 @@ using MQTTnet.Implementations;
using MQTTnet.ManagedClient; using MQTTnet.ManagedClient;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
using MqttClientConnectedEventArgs = MQTTnet.Client.MqttClientConnectedEventArgs;
using MqttClientDisconnectedEventArgs = MQTTnet.Client.MqttClientDisconnectedEventArgs;
namespace MQTTnet.TestApp.UniversalWindows namespace MQTTnet.TestApp.UniversalWindows
{ {
@@ -33,6 +35,11 @@ namespace MQTTnet.TestApp.UniversalWindows
private async void OnTraceMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs e) private async void OnTraceMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs e)
{ {
_traceMessages.Enqueue(e.TraceMessage); _traceMessages.Enqueue(e.TraceMessage);
await UpdateLogAsync();
}
private async Task UpdateLogAsync()
{
while (_traceMessages.Count > 100) while (_traceMessages.Count > 100)
{ {
_traceMessages.TryDequeue(out _); _traceMessages.TryDequeue(out _);
@@ -113,11 +120,15 @@ namespace MQTTnet.TestApp.UniversalWindows
{ {
await _mqttClient.DisconnectAsync(); await _mqttClient.DisconnectAsync();
_mqttClient.ApplicationMessageReceived -= OnApplicationMessageReceived; _mqttClient.ApplicationMessageReceived -= OnApplicationMessageReceived;
_mqttClient.Connected -= OnConnected;
_mqttClient.Disconnected -= OnDisconnected;
} }
var factory = new MqttFactory(); var factory = new MqttFactory();
_mqttClient = factory.CreateMqttClient(); _mqttClient = factory.CreateMqttClient();
_mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
_mqttClient.Connected += OnConnected;
_mqttClient.Disconnected += OnDisconnected;
await _mqttClient.ConnectAsync(options); await _mqttClient.ConnectAsync(options);
} }
@@ -127,6 +138,22 @@ namespace MQTTnet.TestApp.UniversalWindows
} }
} }
private void OnDisconnected(object sender, MqttClientDisconnectedEventArgs e)
{
_traceMessages.Enqueue(new MqttNetLogMessage("", DateTime.Now, -1,
"", MqttNetLogLevel.Info, "! DISCONNECTED EVENT FIRED", null));
Task.Run(UpdateLogAsync);
}
private void OnConnected(object sender, MqttClientConnectedEventArgs e)
{
_traceMessages.Enqueue(new MqttNetLogMessage("", DateTime.Now, -1,
"", MqttNetLogLevel.Info, "! CONNECTED EVENT FIRED", null));
Task.Run(UpdateLogAsync);
}
private async void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs) private async void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
{ {
var item = $"Timestamp: {DateTime.Now:O} | Topic: {eventArgs.ApplicationMessage.Topic} | Payload: {Encoding.UTF8.GetString(eventArgs.ApplicationMessage.Payload)} | QoS: {eventArgs.ApplicationMessage.QualityOfServiceLevel}"; var item = $"Timestamp: {DateTime.Now:O} | Topic: {eventArgs.ApplicationMessage.Topic} | Payload: {Encoding.UTF8.GetString(eventArgs.ApplicationMessage.Payload)} | QoS: {eventArgs.ApplicationMessage.QualityOfServiceLevel}";