mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-20 19:58:57 +08:00
Merge branch 'develop' into ServerShutdown
This commit is contained in:
@@ -11,11 +11,11 @@
|
|||||||
<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] Performance optimizations.
|
||||||
* [Core] Fixed a bug which prevents receiving large packets (UWP only)
|
* [Client] Added support for proxies when using web socket connections (thanks to PitySOFT).
|
||||||
* [Client] The ManagedClient options now allow configuring the interval for connection checks.
|
* [Client] Refactored TLS parameter usage and added more parameters (thanks to PitySOFT).
|
||||||
* [Server] Added the Endpoint of the Adapter (remote IP and port) to the connection validation callback.
|
* [Server] Fixed an issue in client keep alive checks (thanks to @jenscski).
|
||||||
* [Server] The ipv4 and ipv6 endpoint can be disabled now by setting the bound IP address to _None_.
|
* [Server] Changed the order of _ClientConnected_ and _ClientDisconnected_ events so that _ClientConnected_ is fired at first (thanks to @jenscski).
|
||||||
* [Server] Fix a bug in the keep alive monitor which caused high CPU load (thanks to @GarageGadget).
|
* [Server] Fixed an iussue which lets the server stop processing messages when using the application message interceptor (thanks to @alamsor).
|
||||||
</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 +56,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>
|
||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,13 +67,13 @@ namespace MQTTnet.Server
|
|||||||
status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived;
|
status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
|
public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
|
||||||
{
|
{
|
||||||
_workerTask = RunInternalAsync(connectPacket, adapter);
|
_workerTask = RunInternalAsync(connectPacket, adapter);
|
||||||
return _workerTask;
|
return _workerTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
|
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));
|
||||||
@@ -138,8 +138,6 @@ namespace MQTTnet.Server
|
|||||||
_cancellationTokenSource?.Dispose();
|
_cancellationTokenSource?.Dispose();
|
||||||
_cancellationTokenSource = null;
|
_cancellationTokenSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _wasCleanDisconnect;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Cleanup()
|
private async Task Cleanup()
|
||||||
@@ -199,6 +197,8 @@ namespace MQTTnet.Server
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_logger.Info("Client '{0}': Session stopped.", ClientId);
|
_logger.Info("Client '{0}': Session stopped.", ClientId);
|
||||||
|
|
||||||
|
_sessionsManager.Server.OnClientDisconnected(ClientId, _wasCleanDisconnect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,12 +447,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)
|
||||||
{
|
{
|
||||||
@@ -253,8 +268,6 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
DeleteSession(clientId);
|
DeleteSession(clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Server.OnClientDisconnected(clientId, wasCleanDisconnect);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -8,13 +8,14 @@ using MQTTnet.Client;
|
|||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -552,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>();
|
||||||
|
|||||||
Reference in New Issue
Block a user