Merge branch 'develop' into client-cancel-task

This commit is contained in:
Christian
2018-09-28 22:49:00 +02:00
committed by GitHub
31 changed files with 557 additions and 154 deletions
+2 -3
View File
@@ -10,13 +10,12 @@
<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.8.1. <releaseNotes>* For more release notes please check the MQTTnet release notes.
* For more release notes please check the MQTTnet release notes.
</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>
<dependency id="MQTTnet" version="2.8.1" /> <dependency id="MQTTnet" version="2.8.2" />
<dependency id="Microsoft.AspNetCore.Connections.Abstractions" version="2.1.0" /> <dependency id="Microsoft.AspNetCore.Connections.Abstractions" version="2.1.0" />
<dependency id="Microsoft.AspNetCore.WebSockets" version="2.0.1" /> <dependency id="Microsoft.AspNetCore.WebSockets" version="2.0.1" />
<dependency id="Microsoft.Extensions.Hosting.Abstractions" version="2.0.1" /> <dependency id="Microsoft.Extensions.Hosting.Abstractions" version="2.0.1" />
@@ -10,13 +10,12 @@
<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 an extension library which provides a managed MQTT client with additional features using MQTTnet.</description> <description>This is an extension library which provides a managed MQTT client with additional features using MQTTnet.</description>
<releaseNotes>* Updated to MQTTnet 2.8.1. <releaseNotes>* For more release notes please check the MQTTnet release notes.
* For more release notes please check the MQTTnet release notes.
</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>
<dependency id="MQTTnet" version="2.8.1" /> <dependency id="MQTTnet" version="2.8.2" />
</dependencies> </dependencies>
</metadata> </metadata>
@@ -34,6 +33,9 @@
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\net452\MQTTnet.Extensions.ManagedClient.*" target="lib\net452\"/> <file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\net452\MQTTnet.Extensions.ManagedClient.*" target="lib\net452\"/>
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net461\"/> <file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net461\"/>
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net462\"/>
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net470\"/>
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net471\"/>
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net472\"/> <file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net472\"/>
</files> </files>
</package> </package>
+5 -3
View File
@@ -10,13 +10,12 @@
<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 an extension library which allows executing synchronous device calls including a response using MQTTnet.</description> <description>This is an extension library which allows executing synchronous device calls including a response using MQTTnet.</description>
<releaseNotes>* Updated to MQTTnet 2.8.1. <releaseNotes>* For more release notes please check the MQTTnet release notes.
* For more release notes please check the MQTTnet release notes.
</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>
<dependency id="MQTTnet" version="2.8.1" /> <dependency id="MQTTnet" version="2.8.2" />
</dependencies> </dependencies>
</metadata> </metadata>
@@ -35,5 +34,8 @@
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net461\"/> <file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net461\"/>
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net462\"/> <file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net462\"/>
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net470\"/>
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net471\"/>
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net472\"/>
</files> </files>
</package> </package>
+5 -6
View File
@@ -10,12 +10,8 @@
<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] Performance optimizations. <releaseNotes>* [Core] Added all factory methods to the factory interface.
* [Core] Fixed a bug which prevents receiving large packets (UWP only) * [Server] Added a new overload for configuring the ASP.net integration (thanks to @JanEggers).
* [Client] The ManagedClient options now allow configuring the interval for connection checks.
* [Server] Added the Endpoint of the Adapter (remote IP and port) to the connection validation callback.
* [Server] The ipv4 and ipv6 endpoint can be disabled now by setting the bound IP address to _None_.
* [Server] Fix a bug in the keep alive monitor which caused high CPU load (thanks to @GarageGadget).
</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>
@@ -56,6 +52,9 @@
<file src="..\Source\MQTTnet\bin\Release\net452\MQTTnet.*" target="lib\net452\"/> <file src="..\Source\MQTTnet\bin\Release\net452\MQTTnet.*" target="lib\net452\"/>
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net461\"/> <file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net461\"/>
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net462\"/>
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net470\"/>
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net471\"/>
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net472\"/> <file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net472\"/>
</files> </files>
</package> </package>
+2 -1
View File
@@ -83,9 +83,10 @@ This project also listed at Open Collective (https://opencollective.com/mqttnet)
This library is used in the following projects: This library is used in the following projects:
* HA4IoT (Open Source Home Automation system for .NET, <https://github.com/chkr1011/HA4IoT>)
* 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) and [iOS](https://itunes.apple.com/us/app/mqtt-tester/id1278621826?mt=8)) * 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))
* HA4IoT (Open Source Home Automation system for .NET, <https://github.com/chkr1011/HA4IoT>) * Wirehome.Core (Open Source Home Automation system for .NET Core, <https://github.com/chkr1011/Wirehome.Core>)
If you use this library and want to see your project here please let me know. If you use this library and want to see your project here please let me know.
@@ -0,0 +1,15 @@
using MQTTnet.Server;
using System;
namespace MQTTnet.AspNetCore
{
public class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
{
public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public IServiceProvider ServiceProvider { get; }
}
}
@@ -20,14 +20,27 @@ namespace MQTTnet.AspNetCore
return services; return services;
} }
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure) public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
{ {
var builder = new MqttServerOptionsBuilder(); services.AddSingleton<IMqttServerOptions>(s => {
var builder = new MqttServerOptionsBuilder();
configure(builder);
return builder.Build();
});
configure(builder); services.AddHostedMqttServer();
services.AddSingleton<IMqttServerOptions>(builder.Build()); return services;
}
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
{
services.AddSingleton<IMqttServerOptions>(s => {
var builder = new AspNetMqttServerOptionsBuilder(s);
configure(builder);
return builder.Build();
});
services.AddHostedMqttServer(); services.AddHostedMqttServer();
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core'">$(TargetFrameworks);uap10.0</TargetFrameworks>
<Product /> <Product />
<Company /> <Company />
<Authors /> <Authors />
@@ -226,7 +226,7 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested && _mqttClient.IsConnected)
{ {
var message = _messageQueue.Take(cancellationToken); var message = _messageQueue.Take(cancellationToken);
if (message == null) if (message == null)
@@ -306,15 +306,15 @@ namespace MQTTnet.Extensions.ManagedClient
try try
{ {
if (subscriptions.Any())
{
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
}
if (unsubscriptions.Any()) if (unsubscriptions.Any())
{ {
await _mqttClient.UnsubscribeAsync(unsubscriptions).ConfigureAwait(false); await _mqttClient.UnsubscribeAsync(unsubscriptions).ConfigureAwait(false);
} }
if (subscriptions.Any())
{
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
}
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -367,7 +367,6 @@ namespace MQTTnet.Extensions.ManagedClient
} }
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
_publishingCancellationToken = cts; _publishingCancellationToken = cts;
Task.Factory.StartNew(() => PublishQueuedMessages(cts.Token), cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); Task.Factory.StartNew(() => PublishQueuedMessages(cts.Token), cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core'">$(TargetFrameworks);uap10.0</TargetFrameworks>
<Product /> <Product />
<Company /> <Company />
<Authors /> <Authors />
@@ -7,5 +7,9 @@ namespace MQTTnet.Client
IMqttClient CreateMqttClient(); IMqttClient CreateMqttClient();
IMqttClient CreateMqttClient(IMqttNetLogger logger); IMqttClient CreateMqttClient(IMqttNetLogger logger);
IMqttClient CreateMqttClient(IMqttClientAdapterFactory adapterFactory);
IMqttClient CreateMqttClient(IMqttNetLogger logger, IMqttClientAdapterFactory adapterFactory);
} }
} }
+21 -7
View File
@@ -25,10 +25,11 @@ namespace MQTTnet.Client
private IMqttClientOptions _options; private IMqttClientOptions _options;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private Task _packetReceiverTask; internal Task _packetReceiverTask;
private Task _keepAliveMessageSenderTask; internal Task _keepAliveMessageSenderTask;
private IMqttChannelAdapter _adapter; private IMqttChannelAdapter _adapter;
private bool _cleanDisconnectInitiated; private bool _cleanDisconnectInitiated;
private TaskCompletionSource<bool> _disconnectReason;
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger) public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
{ {
@@ -54,6 +55,7 @@ namespace MQTTnet.Client
try try
{ {
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
_disconnectReason = new TaskCompletionSource<bool>();
_options = options; _options = options;
_packetIdentifierProvider.Reset(); _packetIdentifierProvider.Reset();
_packetDispatcher.Reset(); _packetDispatcher.Reset();
@@ -85,8 +87,10 @@ namespace MQTTnet.Client
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error(exception, "Error while connecting with server."); _logger.Error(exception, "Error while connecting with server.");
await DisconnectInternalAsync(null, exception).ConfigureAwait(false); if (_disconnectReason.TrySetException(exception))
{
await DisconnectInternalAsync(null, exception).ConfigureAwait(false);
}
throw; throw;
} }
} }
@@ -104,7 +108,10 @@ namespace MQTTnet.Client
} }
finally finally
{ {
await DisconnectInternalAsync(null, null).ConfigureAwait(false); if (_disconnectReason.TrySetCanceled())
{
await DisconnectInternalAsync(null, null).ConfigureAwait(false);
}
} }
} }
@@ -351,7 +358,10 @@ namespace MQTTnet.Client
_logger.Error(exception, "Unhandled exception while sending/receiving keep alive packets."); _logger.Error(exception, "Unhandled exception while sending/receiving keep alive packets.");
} }
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false); if (_disconnectReason.TrySetException(exception))
{
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
}
} }
finally finally
{ {
@@ -395,7 +405,11 @@ namespace MQTTnet.Client
} }
_packetDispatcher.Dispatch(exception); _packetDispatcher.Dispatch(exception);
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
if (_disconnectReason.TrySetException(exception))
{
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
}
} }
finally finally
{ {
@@ -7,10 +7,11 @@ namespace MQTTnet.Client
public class MqttClientOptionsBuilder public class MqttClientOptionsBuilder
{ {
private readonly MqttClientOptions _options = new MqttClientOptions(); private readonly MqttClientOptions _options = new MqttClientOptions();
private MqttClientTcpOptions _tcpOptions; private MqttClientTcpOptions _tcpOptions;
private MqttClientWebSocketOptions _webSocketOptions; private MqttClientWebSocketOptions _webSocketOptions;
private MqttClientOptionsBuilderTlsParameters _tlsParameters;
private MqttClientTlsOptions _tlsOptions; private MqttClientWebSocketProxyOptions _proxyOptions;
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value) public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
{ {
@@ -76,6 +77,21 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithProxy(string address, string username = null, string password = null, string domain = null, bool bypassOnLocal = false, string[] bypassList = null)
{
_proxyOptions = new MqttClientWebSocketProxyOptions
{
Address = address,
Username = username,
Password = password,
Domain = domain,
BypassOnLocal = bypassOnLocal,
BypassList = bypassList
};
return this;
}
public MqttClientOptionsBuilder WithWebSocketServer(string uri) public MqttClientOptionsBuilder WithWebSocketServer(string uri)
{ {
_webSocketOptions = new MqttClientWebSocketOptions _webSocketOptions = new MqttClientWebSocketOptions
@@ -86,13 +102,25 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters)
{
_tlsParameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
return this;
}
public MqttClientOptionsBuilder WithTls()
{
return WithTls(new MqttClientOptionsBuilderTlsParameters { UseTls = true });
}
[Obsolete("Use method _WithTls_ which accepts the _MqttClientOptionsBuilderTlsParameters_.")]
public MqttClientOptionsBuilder WithTls( public MqttClientOptionsBuilder WithTls(
bool allowUntrustedCertificates = false, bool allowUntrustedCertificates = false,
bool ignoreCertificateChainErrors = false, bool ignoreCertificateChainErrors = false,
bool ignoreCertificateRevocationErrors = false, bool ignoreCertificateRevocationErrors = false,
params byte[][] certificates) params byte[][] certificates)
{ {
_tlsOptions = new MqttClientTlsOptions _tlsParameters = new MqttClientOptionsBuilderTlsParameters
{ {
UseTls = true, UseTls = true,
AllowUntrustedCertificates = allowUntrustedCertificates, AllowUntrustedCertificates = allowUntrustedCertificates,
@@ -104,33 +132,47 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithTls()
{
_tlsOptions = new MqttClientTlsOptions
{
UseTls = true
};
return this;
}
public IMqttClientOptions Build() public IMqttClientOptions Build()
{ {
if (_tlsOptions != null) if (_tcpOptions == null && _webSocketOptions == null)
{ {
if (_tcpOptions == null && _webSocketOptions == null) throw new InvalidOperationException("A channel must be set.");
}
if (_tlsParameters != null)
{
if (_tlsParameters?.UseTls == true)
{ {
throw new InvalidOperationException("A channel (TCP or WebSocket) must be set if TLS is configured."); var tlsOptions = new MqttClientTlsOptions
{
UseTls = true,
SslProtocol = _tlsParameters.SslProtocol,
AllowUntrustedCertificates = _tlsParameters.AllowUntrustedCertificates,
Certificates = _tlsParameters.Certificates?.Select(c => c.ToArray()).ToList(),
CertificateValidationCallback = _tlsParameters.CertificateValidationCallback,
IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors,
IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors
};
if (_tcpOptions != null)
{
_tcpOptions.TlsOptions = tlsOptions;
}
else if (_webSocketOptions != null)
{
_webSocketOptions.TlsOptions = tlsOptions;
}
}
}
if (_proxyOptions != null)
{
if (_webSocketOptions == null)
{
throw new InvalidOperationException("Proxies are only supported for WebSocket connections.");
} }
if (_tcpOptions != null) _webSocketOptions.ProxyOptions = _proxyOptions;
{
_tcpOptions.TlsOptions = _tlsOptions;
}
else if (_webSocketOptions != null)
{
_webSocketOptions.TlsOptions = _tlsOptions;
}
} }
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions; _options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
namespace MQTTnet.Client
{
public class MqttClientOptionsBuilderTlsParameters
{
public bool UseTls { get; set; }
public Func<X509Certificate, X509Chain, SslPolicyErrors, IMqttClientOptions, bool> CertificateValidationCallback
{
get;
set;
}
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
public IEnumerable<IEnumerable<byte>> Certificates { get; set; }
public bool AllowUntrustedCertificates { get; set; }
public bool IgnoreCertificateChainErrors { get; set; }
public bool IgnoreCertificateRevocationErrors { get; set; }
}
}
@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Security; using System.Net.Security;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Security.Authentication;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
@@ -17,6 +18,8 @@ namespace MQTTnet.Client
public List<byte[]> Certificates { get; set; } public List<byte[]> Certificates { get; set; }
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
public Func<X509Certificate, X509Chain, SslPolicyErrors, IMqttClientOptions, bool> CertificateValidationCallback { get; set; } public Func<X509Certificate, X509Chain, SslPolicyErrors, IMqttClientOptions, bool> CertificateValidationCallback { get; set; }
} }
} }
@@ -13,6 +13,8 @@ namespace MQTTnet.Client
public CookieContainer CookieContainer { get; set; } public CookieContainer CookieContainer { get; set; }
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
public override string ToString() public override string ToString()
@@ -0,0 +1,17 @@
namespace MQTTnet.Client
{
public class MqttClientWebSocketProxyOptions
{
public string Address { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Domain { get; set; }
public bool BypassOnLocal { get; set; }
public string[] BypassList { get; set; }
}
}
@@ -63,7 +63,7 @@ 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(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); await sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), _options.TlsOptions.SslProtocol, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
} }
CreateStream(sslStream); CreateStream(sslStream);
@@ -87,8 +87,14 @@ namespace MQTTnet.Implementations
public void Dispose() public void Dispose()
{ {
TryDispose(_stream, () => _stream = null); Cleanup(ref _stream, (s) => s.Dispose());
TryDispose(_socket, () => _socket = null); Cleanup(ref _socket, (s) => {
if (s.Connected)
{
s.Shutdown(SocketShutdown.Both);
}
s.Dispose();
});
} }
private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
@@ -157,11 +163,16 @@ namespace MQTTnet.Implementations
} }
} }
private static void TryDispose(IDisposable disposable, Action afterDispose) private static void Cleanup<T>(ref T item, Action<T> handler) where T : class
{ {
var temp = item;
item = null;
try try
{ {
disposable?.Dispose(); if (temp != null)
{
handler(temp);
}
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
@@ -169,10 +180,6 @@ namespace MQTTnet.Implementations
catch (NullReferenceException) catch (NullReferenceException)
{ {
} }
finally
{
afterDispose();
}
} }
} }
} }
@@ -78,8 +78,10 @@ namespace MQTTnet.Implementations
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
} }
var protocol = _addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6"; _logger.Verbose("Client '{0}' accepted by TCP listener '{1}, {2}'.",
_logger.Verbose($"Client '{clientSocket.RemoteEndPoint}' accepted by TCP listener '{_socket.LocalEndPoint}, {protocol}'."); clientSocket.RemoteEndPoint,
_socket.LocalEndPoint,
_addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6");
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger); var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger);
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
@@ -1,4 +1,5 @@
using System; using System;
using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
@@ -14,7 +15,7 @@ namespace MQTTnet.Implementations
private readonly MqttClientWebSocketOptions _options; private readonly MqttClientWebSocketOptions _options;
private WebSocket _webSocket; private WebSocket _webSocket;
public MqttWebSocketChannel(MqttClientWebSocketOptions options) public MqttWebSocketChannel(MqttClientWebSocketOptions options)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
@@ -45,6 +46,11 @@ namespace MQTTnet.Implementations
var clientWebSocket = new ClientWebSocket(); var clientWebSocket = new ClientWebSocket();
if (_options.ProxyOptions != null)
{
clientWebSocket.Options.Proxy = CreateProxy();
}
if (_options.RequestHeaders != null) if (_options.RequestHeaders != null)
{ {
foreach (var requestHeader in _options.RequestHeaders) foreach (var requestHeader in _options.RequestHeaders)
@@ -132,5 +138,31 @@ namespace MQTTnet.Implementations
_webSocket = null; _webSocket = null;
} }
} }
private IWebProxy CreateProxy()
{
if (string.IsNullOrEmpty(_options.ProxyOptions?.Address))
{
return null;
}
#if WINDOWS_UWP
throw new NotSupportedException("Proxies are not supported in UWP.");
#elif NETSTANDARD1_3
throw new NotSupportedException("Proxies are not supported in netstandard 1.3.");
#else
var proxyUri = new Uri(_options.ProxyOptions.Address);
if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password))
{
var credentials =
new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain);
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList, credentials);
}
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
#endif
}
} }
} }
+3 -2
View File
@@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.3;netstandard2.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core'">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet</AssemblyName> <AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace> <RootNamespace>MQTTnet</RootNamespace>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> <GeneratePackageOnBuild>False</GeneratePackageOnBuild>
@@ -0,0 +1,5 @@
using System.Runtime.CompilerServices;
#if DEBUG
[assembly:InternalsVisibleTo("MQTTnet.Core.Tests")]
#endif
+1 -1
View File
@@ -14,7 +14,7 @@ namespace MQTTnet.Server
void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket); void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket);
void ClearPendingApplicationMessages(); void ClearPendingApplicationMessages();
Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter); Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter);
void Stop(MqttClientDisconnectType disconnectType); void Stop(MqttClientDisconnectType disconnectType);
Task SubscribeAsync(IList<TopicFilter> topicFilters); Task SubscribeAsync(IList<TopicFilter> topicFilters);
@@ -8,6 +8,8 @@ namespace MQTTnet.Server
{ {
IMqttServer CreateMqttServer(); IMqttServer CreateMqttServer();
IMqttServer CreateMqttServer(IMqttNetLogger logger);
IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger); IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger);
} }
} }
@@ -14,9 +14,9 @@ namespace MQTTnet.Server
private readonly IMqttClientSession _clientSession; private readonly IMqttClientSession _clientSession;
private readonly IMqttNetChildLogger _logger; private readonly IMqttNetChildLogger _logger;
private bool _isPaused; private bool _isPaused;
public MqttClientKeepAliveMonitor(IMqttClientSession clientSession, IMqttNetChildLogger logger) public MqttClientKeepAliveMonitor(IMqttClientSession clientSession, IMqttNetChildLogger logger)
{ {
if (logger == null) throw new ArgumentNullException(nameof(logger)); if (logger == null) throw new ArgumentNullException(nameof(logger));
@@ -76,7 +76,7 @@ namespace MQTTnet.Server
{ {
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientSession.ClientId); _logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientSession.ClientId);
_clientSession.Stop(MqttClientDisconnectType.NotClean); _clientSession.Stop(MqttClientDisconnectType.NotClean);
return; return;
} }
@@ -96,7 +96,7 @@ namespace MQTTnet.Server
} }
finally finally
{ {
_logger.Verbose("Client {0}: Stopped checking keep alive timeout.", _clientSession.ClientId); _logger.Verbose("Client '{0}': Stopped checking keep alive timeout.", _clientSession.ClientId);
} }
} }
} }
+67 -15
View File
@@ -29,6 +29,8 @@ namespace MQTTnet.Server
private MqttApplicationMessage _willMessage; private MqttApplicationMessage _willMessage;
private bool _wasCleanDisconnect; private bool _wasCleanDisconnect;
private IMqttChannelAdapter _adapter; private IMqttChannelAdapter _adapter;
private Task _workerTask;
private IDisposable _cleanupHandle;
public MqttClientSession( public MqttClientSession(
string clientId, string clientId,
@@ -65,7 +67,13 @@ namespace MQTTnet.Server
status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived;
} }
public async Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
{
_workerTask = RunInternalAsync(connectPacket, adapter);
return _workerTask;
}
private async Task RunInternalAsync(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));
@@ -73,10 +81,18 @@ namespace MQTTnet.Server
try try
{ {
_adapter = adapter; _adapter = adapter;
adapter.ReadingPacketStarted += OnAdapterReadingPacketStarted; adapter.ReadingPacketStarted += OnAdapterReadingPacketStarted;
adapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted; adapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted;
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
//woraround for https://github.com/dotnet/corefx/issues/24430
#pragma warning disable 4014
_cleanupHandle = _cancellationTokenSource.Token.Register(() => CleanupAsync());
#pragma warning restore 4014
//endworkaround
_wasCleanDisconnect = false; _wasCleanDisconnect = false;
_willMessage = connectPacket.WillMessage; _willMessage = connectPacket.WillMessage;
@@ -114,26 +130,55 @@ namespace MQTTnet.Server
_logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId); _logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId);
} }
Stop(MqttClientDisconnectType.NotClean); Stop(MqttClientDisconnectType.NotClean, true);
} }
finally finally
{ {
if (_adapter != null) await CleanupAsync().ConfigureAwait(false);
{
_adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted;
_adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted;
}
_cleanupHandle?.Dispose();
_cleanupHandle = null;
_adapter = null; _adapter = null;
_cancellationTokenSource?.Cancel (); _cancellationTokenSource?.Cancel(false);
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
_cancellationTokenSource = null; _cancellationTokenSource = null;
} }
}
return _wasCleanDisconnect; private async Task CleanupAsync()
{
var adapter = _adapter;
try
{
if (adapter == null)
{
return;
}
_adapter = null;
adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted;
adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted;
await adapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, exception.Message);
}
finally
{
adapter?.Dispose();
}
} }
public void Stop(MqttClientDisconnectType type) public void Stop(MqttClientDisconnectType type)
{
Stop(type, false);
}
private void Stop(MqttClientDisconnectType type, bool isInsideSession)
{ {
try try
{ {
@@ -153,10 +198,17 @@ namespace MQTTnet.Server
} }
_willMessage = null; _willMessage = null;
if (!isInsideSession)
{
_workerTask?.GetAwaiter().GetResult();
}
} }
finally finally
{ {
_logger.Info("Client '{0}': Session stopped.", ClientId); _logger.Info("Client '{0}': Session stopped.", ClientId);
_sessionsManager.Server.OnClientDisconnected(ClientId, _wasCleanDisconnect);
} }
} }
@@ -300,18 +352,18 @@ namespace MQTTnet.Server
if (packet is MqttDisconnectPacket) if (packet is MqttDisconnectPacket)
{ {
Stop(MqttClientDisconnectType.Clean); Stop(MqttClientDisconnectType.Clean, true);
return; return;
} }
if (packet is MqttConnectPacket) if (packet is MqttConnectPacket)
{ {
Stop(MqttClientDisconnectType.NotClean); Stop(MqttClientDisconnectType.NotClean, true);
return; return;
} }
_logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); _logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
Stop(MqttClientDisconnectType.NotClean); Stop(MqttClientDisconnectType.NotClean, true);
} }
private void EnqueueSubscribedRetainedMessages(ICollection<TopicFilter> topicFilters) private void EnqueueSubscribedRetainedMessages(ICollection<TopicFilter> topicFilters)
@@ -330,7 +382,7 @@ namespace MQTTnet.Server
if (subscribeResult.CloseConnection) if (subscribeResult.CloseConnection)
{ {
Stop(MqttClientDisconnectType.NotClean); Stop(MqttClientDisconnectType.NotClean, true);
return; return;
} }
@@ -407,12 +459,12 @@ namespace MQTTnet.Server
private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) private void OnAdapterReadingPacketCompleted(object sender, EventArgs e)
{ {
_keepAliveMonitor?.Pause(); _keepAliveMonitor?.Resume();
} }
private void OnAdapterReadingPacketStarted(object sender, EventArgs e) private void OnAdapterReadingPacketStarted(object sender, EventArgs e)
{ {
_keepAliveMonitor?.Resume(); _keepAliveMonitor?.Pause();
} }
} }
} }
@@ -44,7 +44,7 @@ namespace MQTTnet.Server
public void Start() public void Start()
{ {
Task.Factory.StartNew(() => ProcessQueuedApplicationMessages(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); Task.Factory.StartNew(() => TryProcessQueuedApplicationMessages(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
} }
public void Stop() public void Stop()
@@ -125,6 +125,7 @@ namespace MQTTnet.Server
{ {
_sessions.Remove(clientId); _sessions.Remove(clientId);
} }
_logger.Verbose("Session for client '{0}' deleted.", clientId); _logger.Verbose("Session for client '{0}' deleted.", clientId);
} }
@@ -133,54 +134,69 @@ namespace MQTTnet.Server
_messageQueue?.Dispose(); _messageQueue?.Dispose();
} }
private void ProcessQueuedApplicationMessages(CancellationToken cancellationToken) private void TryProcessQueuedApplicationMessages(CancellationToken cancellationToken)
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
try try
{ {
var enqueuedApplicationMessage = _messageQueue.Take(cancellationToken); TryProcessNextQueuedApplicationMessage(cancellationToken);
var sender = enqueuedApplicationMessage.Sender;
var applicationMessage = enqueuedApplicationMessage.PublishPacket.ToApplicationMessage();
var interceptorContext = InterceptApplicationMessage(sender, applicationMessage);
if (interceptorContext != null)
{
if (interceptorContext.CloseConnection)
{
enqueuedApplicationMessage.Sender.Stop(MqttClientDisconnectType.NotClean);
}
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
{
return;
}
applicationMessage = interceptorContext.ApplicationMessage;
}
Server.OnApplicationMessageReceived(sender?.ClientId, applicationMessage);
if (applicationMessage.Retain)
{
_retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).GetAwaiter().GetResult();
}
foreach (var clientSession in GetSessions())
{
clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage.ToPublishPacket());
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error(exception, "Unhandled exception while processing queued application message."); _logger.Error(exception, "Unhandled exception while processing queued application messages.");
} }
} }
} }
private void TryProcessNextQueuedApplicationMessage(CancellationToken cancellationToken)
{
try
{
var enqueuedApplicationMessage = _messageQueue.Take(cancellationToken);
var sender = enqueuedApplicationMessage.Sender;
var applicationMessage = enqueuedApplicationMessage.PublishPacket.ToApplicationMessage();
var interceptorContext = InterceptApplicationMessage(sender, applicationMessage);
if (interceptorContext != null)
{
if (interceptorContext.CloseConnection)
{
enqueuedApplicationMessage.Sender.Stop(MqttClientDisconnectType.NotClean);
}
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
{
return;
}
applicationMessage = interceptorContext.ApplicationMessage;
}
Server.OnApplicationMessageReceived(sender?.ClientId, applicationMessage);
if (applicationMessage.Retain)
{
_retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).GetAwaiter().GetResult();
}
foreach (var clientSession in GetSessions())
{
clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage.ToPublishPacket());
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error(exception, "Unhandled exception while processing next queued application message.");
}
}
private List<MqttClientSession> GetSessions() private List<MqttClientSession> GetSessions()
{ {
lock (_sessions) lock (_sessions)
@@ -192,8 +208,7 @@ namespace MQTTnet.Server
private async Task RunSession(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) private async Task RunSession(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
{ {
var clientId = string.Empty; var clientId = string.Empty;
var wasCleanDisconnect = false;
try try
{ {
var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false);
@@ -238,7 +253,7 @@ namespace MQTTnet.Server
Server.OnClientConnected(clientId); Server.OnClientConnected(clientId);
wasCleanDisconnect = await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -249,22 +264,10 @@ namespace MQTTnet.Server
} }
finally finally
{ {
try
{
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
clientAdapter.Dispose();
}
catch (Exception exception)
{
_logger.Error(exception, exception.Message);
}
if (!_options.EnablePersistentSessions) if (!_options.EnablePersistentSessions)
{ {
DeleteSession(clientId); DeleteSession(clientId);
} }
Server.OnClientDisconnected(clientId, wasCleanDisconnect);
} }
} }
+2 -2
View File
@@ -106,14 +106,14 @@ namespace MQTTnet.Server
_cancellationTokenSource.Cancel(false); _cancellationTokenSource.Cancel(false);
_clientSessionsManager.Stop();
foreach (var adapter in _adapters) foreach (var adapter in _adapters)
{ {
adapter.ClientAccepted -= OnClientAccepted; adapter.ClientAccepted -= OnClientAccepted;
await adapter.StopAsync().ConfigureAwait(false); await adapter.StopAsync().ConfigureAwait(false);
} }
_clientSessionsManager.Stop();
_logger.Info("Stopped."); _logger.Info("Stopped.");
Stopped?.Invoke(this, EventArgs.Empty); Stopped?.Invoke(this, EventArgs.Empty);
} }
+46 -6
View File
@@ -1,25 +1,29 @@
using System.Net.Sockets; using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Packets;
namespace MQTTnet.Core.Tests namespace MQTTnet.Core.Tests
{ {
[TestClass] [TestClass]
public class MqttClientTests public class MqttClientTests
{ {
[TestMethod] [TestMethod]
public async Task ClientDisconnectException() public async Task ClientDisconnectException()
{ {
var factory = new MqttFactory(); var factory = new MqttFactory();
var client = factory.CreateMqttClient(); var client = factory.CreateMqttClient();
var exceptionIsCorrect = false; Exception ex = null;
client.Disconnected += (s, e) => client.Disconnected += (s, e) =>
{ {
exceptionIsCorrect = e.Exception is MqttCommunicationException c && c.InnerException is SocketException; ex = e.Exception;
}; };
try try
@@ -29,8 +33,44 @@ namespace MQTTnet.Core.Tests
catch catch
{ {
} }
Assert.IsTrue(exceptionIsCorrect); Assert.IsNotNull(ex);
Assert.IsInstanceOfType(ex, typeof(MqttCommunicationException));
Assert.IsInstanceOfType(ex.InnerException, typeof(SocketException));
} }
#if DEBUG
[TestMethod]
public async Task ClientCleanupOnAuthentificationFails()
{
var channel = new TestMqttCommunicationAdapter();
var channel2 = new TestMqttCommunicationAdapter();
channel.Partner = channel2;
channel2.Partner = channel;
Task.Run(async () => {
var connect = await channel2.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None);
await channel2.SendPacketAsync(new MqttConnAckPacket() { ConnectReturnCode = Protocol.MqttConnectReturnCode.ConnectionRefusedNotAuthorized }, CancellationToken.None);
});
var fake = new TestMqttCommunicationAdapterFactory(channel);
var client = new MqttClient(fake, new MqttNetLogger());
try
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("any-server").Build());
}
catch (Exception ex)
{
Assert.IsInstanceOfType(ex, typeof(MqttConnectingFailedException));
}
Assert.IsTrue(client._packetReceiverTask == null || client._packetReceiverTask.IsCompleted, "receive loop not completed");
Assert.IsTrue(client._keepAliveMessageSenderTask == null || client._keepAliveMessageSenderTask.IsCompleted, "keepalive loop not completed");
}
#endif
} }
} }
@@ -60,7 +60,7 @@ namespace MQTTnet.Core.Tests
{ {
public string ClientId { get; } public string ClientId { get; }
public int StopCalledCount { get; set; } public int StopCalledCount { get; private set; }
public void FillStatus(MqttClientSessionStatus status) public void FillStatus(MqttClientSessionStatus status)
{ {
@@ -77,7 +77,7 @@ namespace MQTTnet.Core.Tests
throw new NotSupportedException(); throw new NotSupportedException();
} }
public Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
+116
View File
@@ -233,6 +233,74 @@ namespace MQTTnet.Core.Tests
await c1.PublishAsync(message); await c1.PublishAsync(message);
} }
} }
[TestMethod]
public async Task MqttServer_ShutdownDisconnectsClientsGracefully()
{
var serverAdapter = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger());
var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger());
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.Build();
var disconnectCalled = 0;
await s.StartAsync(new MqttServerOptions());
var c1 = new MqttFactory().CreateMqttClient();
c1.Disconnected += (sender, args) => disconnectCalled++;
await c1.ConnectAsync(clientOptions);
await Task.Delay(100);
await s.StopAsync();
await Task.Delay(100);
Assert.AreEqual(1, disconnectCalled);
}
[TestMethod]
public async Task MqttServer_HandleCleanDisconnect()
{
MqttNetGlobalLogger.LogMessagePublished += (_, e) =>
{
System.Diagnostics.Debug.WriteLine($"[{e.TraceMessage.Timestamp:s}] {e.TraceMessage.Source} {e.TraceMessage.Message}");
};
var serverAdapter = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger());
var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger());
var clientConnectedCalled = 0;
var clientDisconnectedCalled = 0;
s.ClientConnected += (_, __) => clientConnectedCalled++;
s.ClientDisconnected += (_, __) => clientDisconnectedCalled++;
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.Build();
await s.StartAsync(new MqttServerOptions());
var c1 = new MqttFactory().CreateMqttClient();
await c1.ConnectAsync(clientOptions);
await Task.Delay(100);
await c1.DisconnectAsync();
await Task.Delay(100);
await s.StopAsync();
await Task.Delay(100);
Assert.AreEqual(clientConnectedCalled, clientDisconnectedCalled);
}
[TestMethod] [TestMethod]
public async Task MqttServer_RetainedMessagesFlow() public async Task MqttServer_RetainedMessagesFlow()
@@ -484,6 +552,54 @@ namespace MQTTnet.Core.Tests
Assert.IsTrue(bodyIsMatching); Assert.IsTrue(bodyIsMatching);
} }
[TestMethod]
public async Task MqttServer_SameClientIdConnectDisconnectEventOrder()
{
var serverAdapter = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger());
var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger());
var connectedClient = false;
var connecteCalledBeforeConnectedClients = false;
s.ClientConnected += (_, __) =>
{
connecteCalledBeforeConnectedClients |= connectedClient;
connectedClient = true;
};
s.ClientDisconnected += (_, __) =>
{
connectedClient = false;
};
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.WithClientId(Guid.NewGuid().ToString())
.Build();
await s.StartAsync(new MqttServerOptions());
var c1 = new MqttFactory().CreateMqttClient();
var c2 = new MqttFactory().CreateMqttClient();
await c1.ConnectAsync(clientOptions);
await Task.Delay(100);
await c2.ConnectAsync(clientOptions);
await Task.Delay(100);
await c1.DisconnectAsync();
await c2.DisconnectAsync();
await s.StopAsync();
await Task.Delay(100);
Assert.IsFalse(connecteCalledBeforeConnectedClients, "ClientConnected was called before ClientDisconnect was called");
}
private class TestStorage : IMqttServerStorage private class TestStorage : IMqttServerStorage
{ {
public IList<MqttApplicationMessage> Messages = new List<MqttApplicationMessage>(); public IList<MqttApplicationMessage> Messages = new List<MqttApplicationMessage>();