From 906a0ba1892762f4c642b03ae095fdccf49a77b5 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 14 Oct 2017 08:11:56 +0200 Subject: [PATCH 01/34] removed duplicated code by using csproj with targetframeworks --- Build/MQTTnet.nuspec | 4 +- .../MqttCommunicationAdapterFactory.cs | 27 --- .../Implementations/MqttServerAdapter.cs | 133 ------------ .../Implementations/MqttTcpChannel.cs | 150 -------------- .../Implementations/MqttWebSocketChannel.cs | 57 ------ .../Implementations/WebSocketStream.cs | 77 ------- .../MQTTnet.NetFramework.csproj | 126 ------------ .../MQTTnet.NetFramework/MqttClientFactory.cs | 13 -- .../MQTTnet.NetFramework/MqttServerFactory.cs | 18 -- .../Properties/AssemblyInfo.cs | 15 -- .../Implementations/MqttServerAdapter.cs | 8 + .../Implementations/MqttTcpChannel.cs | 4 + .../MQTTnet.Netstandard.csproj | 4 +- MQTTnet.sln | 38 ---- .../MQTTnet.TestApp.NetCore.csproj | 2 +- .../PerformanceTest.cs | 2 +- Tests/MQTTnet.TestApp.NetCore/Program.cs | 6 + Tests/MQTTnet.TestApp.NetFramework/App.config | 6 - .../MQTTnet.TestApp.NetFramework.csproj | 103 ---------- Tests/MQTTnet.TestApp.NetFramework/Program.cs | 189 ------------------ .../Properties/AssemblyInfo.cs | 15 -- 21 files changed, 24 insertions(+), 973 deletions(-) delete mode 100644 Frameworks/MQTTnet.NetFramework/Implementations/MqttCommunicationAdapterFactory.cs delete mode 100644 Frameworks/MQTTnet.NetFramework/Implementations/MqttServerAdapter.cs delete mode 100644 Frameworks/MQTTnet.NetFramework/Implementations/MqttTcpChannel.cs delete mode 100644 Frameworks/MQTTnet.NetFramework/Implementations/MqttWebSocketChannel.cs delete mode 100644 Frameworks/MQTTnet.NetFramework/Implementations/WebSocketStream.cs delete mode 100644 Frameworks/MQTTnet.NetFramework/MQTTnet.NetFramework.csproj delete mode 100644 Frameworks/MQTTnet.NetFramework/MqttClientFactory.cs delete mode 100644 Frameworks/MQTTnet.NetFramework/MqttServerFactory.cs delete mode 100644 Frameworks/MQTTnet.NetFramework/Properties/AssemblyInfo.cs rename Tests/{MQTTnet.TestApp.NetFramework => MQTTnet.TestApp.NetCore}/PerformanceTest.cs (99%) delete mode 100644 Tests/MQTTnet.TestApp.NetFramework/App.config delete mode 100644 Tests/MQTTnet.TestApp.NetFramework/MQTTnet.TestApp.NetFramework.csproj delete mode 100644 Tests/MQTTnet.TestApp.NetFramework/Program.cs delete mode 100644 Tests/MQTTnet.TestApp.NetFramework/Properties/AssemblyInfo.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index d6f72810..c7a9b86d 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -48,8 +48,8 @@ - - + + \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetFramework/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.NetFramework/Implementations/MqttCommunicationAdapterFactory.cs deleted file mode 100644 index 799ccddd..00000000 --- a/Frameworks/MQTTnet.NetFramework/Implementations/MqttCommunicationAdapterFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using MQTTnet.Core.Adapter; -using MQTTnet.Core.Client; -using MQTTnet.Core.Serializer; - -namespace MQTTnet.Implementations -{ - public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory - { - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - - if (options is MqttClientTcpOptions tcpOptions) - { - return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); - } - - if (options is MqttClientWebSocketOptions webSocketOptions) - { - return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); - } - - throw new NotSupportedException(); - } - } -} \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetFramework/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.NetFramework/Implementations/MqttServerAdapter.cs deleted file mode 100644 index c6df6efc..00000000 --- a/Frameworks/MQTTnet.NetFramework/Implementations/MqttServerAdapter.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Net; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Serializer; -using MQTTnet.Core.Server; - -namespace MQTTnet.Implementations -{ - public class MqttServerAdapter : IMqttServerAdapter, IDisposable - { - private CancellationTokenSource _cancellationTokenSource; - private Socket _defaultEndpointSocket; - private Socket _tlsEndpointSocket; - private X509Certificate2 _tlsCertificate; - - private bool _isRunning; - - public event Action ClientAccepted; - - public Task StartAsync(MqttServerOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - - if (_isRunning) throw new InvalidOperationException("Server is already started."); - _isRunning = true; - - _cancellationTokenSource = new CancellationTokenSource(); - - if (options.DefaultEndpointOptions.IsEnabled) - { - _defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); - _defaultEndpointSocket.Bind(new IPEndPoint(IPAddress.Any, options.GetDefaultEndpointPort())); - _defaultEndpointSocket.Listen(options.ConnectionBacklog); - - Task.Run(() => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); - } - - if (options.TlsEndpointOptions.IsEnabled) - { - if (options.TlsEndpointOptions.Certificate == null) - { - throw new ArgumentException("TLS certificate is not set."); - } - - _tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate); - - _tlsEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); - _tlsEndpointSocket.Bind(new IPEndPoint(IPAddress.Any, options.GetTlsEndpointPort())); - _tlsEndpointSocket.Listen(options.ConnectionBacklog); - - Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); - } - - return Task.FromResult(0); - } - - public Task StopAsync() - { - _isRunning = false; - - _cancellationTokenSource?.Cancel(false); - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; - - _defaultEndpointSocket?.Dispose(); - _defaultEndpointSocket = null; - - _tlsEndpointSocket?.Dispose(); - _tlsEndpointSocket = null; - - return Task.FromResult(0); - } - - public void Dispose() - { - StopAsync(); - } - - private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - try - { - var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false); - - var tcpChannel = new MqttTcpChannel(clientSocket, null); - var clientAdapter = new MqttChannelCommunicationAdapter(tcpChannel, new MqttPacketSerializer()); - ClientAccepted?.Invoke(clientAdapter); - } - catch (Exception exception) when (!(exception is ObjectDisposedException)) - { - MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint."); - - //excessive CPU consumed if in endless loop of socket errors - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); - } - } - } - - private async Task AcceptTlsEndpointConnectionsAsync(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - try - { - var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null).ConfigureAwait(false); - - var sslStream = new SslStream(new NetworkStream(clientSocket)); - await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); - - var tcpChannel = new MqttTcpChannel(clientSocket, sslStream); - var clientAdapter = new MqttChannelCommunicationAdapter(tcpChannel, new MqttPacketSerializer()); - ClientAccepted?.Invoke(clientAdapter); - } - catch (Exception exception) - { - MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint."); - - //excessive CPU consumed if in endless loop of socket errors - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); - } - } - } - } -} \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetFramework/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetFramework/Implementations/MqttTcpChannel.cs deleted file mode 100644 index 6f9375ef..00000000 --- a/Frameworks/MQTTnet.NetFramework/Implementations/MqttTcpChannel.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; -using MQTTnet.Core.Channel; -using MQTTnet.Core.Client; -using System.IO; -using System.Linq; - -namespace MQTTnet.Implementations -{ - public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable - { - private readonly MqttClientTcpOptions _options; - - // 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 Socket _socket; - private SslStream _sslStream; - - /// - /// called on client sockets are created in connect - /// - public MqttTcpChannel(MqttClientTcpOptions options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - /// - /// called on server, sockets are passed in - /// connect will not be called - /// - public MqttTcpChannel(Socket socket, SslStream sslStream) - { - _socket = socket ?? throw new ArgumentNullException(nameof(socket)); - _sslStream = sslStream; - - CreateStreams(socket, sslStream); - } - - public Stream SendStream { get; private set; } - public Stream ReceiveStream { get; private set; } - public Stream RawReceiveStream { get; private set; } - - public static Func CustomCertificateValidationCallback { get; set; } - - public async Task ConnectAsync() - { - if (_socket == null) - { - _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); - } - - await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false); - - if (_options.TlsOptions.UseTls) - { - _sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); - await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); - } - - CreateStreams(_socket, _sslStream); - } - - public Task DisconnectAsync() - { - Dispose(); - return Task.FromResult(0); - } - - public void Dispose() - { - RawReceiveStream?.Dispose(); - RawReceiveStream = null; - - ReceiveStream?.Dispose(); - ReceiveStream = null; - - SendStream?.Dispose(); - SendStream = null; - - _socket?.Dispose(); - _socket = null; - - _sslStream?.Dispose(); - _sslStream = null; - } - - private void CreateStreams(Socket socket, Stream sslStream) - { - RawReceiveStream = sslStream ?? new NetworkStream(socket); - - //cannot use this as default buffering prevents from receiving the first connect message - //need two streams otherwise read and write have to be synchronized - SendStream = new BufferedStream(RawReceiveStream, BufferSize); - ReceiveStream = new BufferedStream(RawReceiveStream, BufferSize); - } - - private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) - { - if (CustomCertificateValidationCallback != null) - { - return CustomCertificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _options); - } - - if (sslPolicyErrors == SslPolicyErrors.None) - { - return true; - } - - if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.RevocationStatusUnknown)) - { - if (!_options.TlsOptions.IgnoreCertificateRevocationErrors) - { - return false; - } - } - - if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.PartialChain)) - { - if (!_options.TlsOptions.IgnoreCertificateChainErrors) - { - return false; - } - } - - return _options.TlsOptions.AllowUntrustedCertificates; - } - - private static X509CertificateCollection LoadCertificates(MqttClientOptions options) - { - var certificates = new X509CertificateCollection(); - if (options.TlsOptions.Certificates == null) - { - return certificates; - } - - foreach (var certificate in options.TlsOptions.Certificates) - { - certificates.Add(new X509Certificate(certificate)); - } - - return certificates; - } - } -} \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetFramework/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.NetFramework/Implementations/MqttWebSocketChannel.cs deleted file mode 100644 index d6c92ac9..00000000 --- a/Frameworks/MQTTnet.NetFramework/Implementations/MqttWebSocketChannel.cs +++ /dev/null @@ -1,57 +0,0 @@ -using MQTTnet.Core.Channel; -using MQTTnet.Core.Client; -using System; -using System.IO; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; - -namespace MQTTnet.Implementations -{ - public sealed class MqttWebSocketChannel : IMqttCommunicationChannel, IDisposable - { - private readonly MqttClientWebSocketOptions _options; - - private ClientWebSocket _webSocket; - - public MqttWebSocketChannel(MqttClientWebSocketOptions options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - public Stream RawReceiveStream { get; private set; } - public Stream SendStream => RawReceiveStream; - public Stream ReceiveStream => RawReceiveStream; - - public async Task ConnectAsync() - { - var uri = _options.Uri; - if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase)) - { - uri = "ws://" + uri; - } - - _webSocket = new ClientWebSocket(); - _webSocket.Options.KeepAliveInterval = _options.KeepAlivePeriod; - await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false); - RawReceiveStream = new WebSocketStream(_webSocket); - } - - public async Task DisconnectAsync() - { - RawReceiveStream = null; - - if (_webSocket == null) - { - return; - } - - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false); - } - - public void Dispose() - { - _webSocket?.Dispose(); - } - } -} \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetFramework/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.NetFramework/Implementations/WebSocketStream.cs deleted file mode 100644 index 4f059360..00000000 --- a/Frameworks/MQTTnet.NetFramework/Implementations/WebSocketStream.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.IO; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; - -namespace MQTTnet.Implementations -{ - public class WebSocketStream : Stream - { - private readonly ClientWebSocket _webSocket; - - public WebSocketStream(ClientWebSocket webSocket) - { - _webSocket = webSocket; - } - - public override void Flush() - { - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - var currentOffset = offset; - var targetOffset = offset + count; - while (_webSocket.State == WebSocketState.Open && currentOffset < targetOffset) - { - var response = await _webSocket.ReceiveAsync(new ArraySegment(buffer, currentOffset, count), cancellationToken).ConfigureAwait(false); - currentOffset += response.Count; - - if (response.MessageType == WebSocketMessageType.Close) - { - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); - } - } - - return currentOffset - offset; - } - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return _webSocket.SendAsync(new ArraySegment(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return ReadAsync(buffer, offset, count).GetAwaiter().GetResult(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - WriteAsync(buffer, offset, count).GetAwaiter().GetResult(); - } - - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => true; - - public override long Length => throw new NotSupportedException(); - - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - } -} diff --git a/Frameworks/MQTTnet.NetFramework/MQTTnet.NetFramework.csproj b/Frameworks/MQTTnet.NetFramework/MQTTnet.NetFramework.csproj deleted file mode 100644 index 22d50325..00000000 --- a/Frameworks/MQTTnet.NetFramework/MQTTnet.NetFramework.csproj +++ /dev/null @@ -1,126 +0,0 @@ - - - - - Debug - AnyCPU - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D} - Library - Properties - MQTTnet - MQTTnet - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - bin\Any CPU\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\x86\Any CPU\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - prompt - MinimumRecommendedRules.ruleset - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - - - bin\x64\Any CPU\ - TRACE - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - - - - - - - - - - - - - - - - - - {2ecb99e4-72d0-4c23-99ba-93d511d3967d} - MQTTnet.Core - - - - - \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetFramework/MqttClientFactory.cs b/Frameworks/MQTTnet.NetFramework/MqttClientFactory.cs deleted file mode 100644 index 15da033d..00000000 --- a/Frameworks/MQTTnet.NetFramework/MqttClientFactory.cs +++ /dev/null @@ -1,13 +0,0 @@ -using MQTTnet.Core.Client; -using MQTTnet.Implementations; - -namespace MQTTnet -{ - public class MqttClientFactory : IMqttClientFactory - { - public IMqttClient CreateMqttClient() - { - return new MqttClient(new MqttCommunicationAdapterFactory()); - } - } -} \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetFramework/MqttServerFactory.cs b/Frameworks/MQTTnet.NetFramework/MqttServerFactory.cs deleted file mode 100644 index 70e14f3c..00000000 --- a/Frameworks/MQTTnet.NetFramework/MqttServerFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using MQTTnet.Core.Adapter; -using MQTTnet.Core.Server; -using MQTTnet.Implementations; - -namespace MQTTnet -{ - public class MqttServerFactory : IMqttServerFactory - { - public IMqttServer CreateMqttServer(MqttServerOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - - return new MqttServer(options, new List { new MqttServerAdapter() }); - } - } -} diff --git a/Frameworks/MQTTnet.NetFramework/Properties/AssemblyInfo.cs b/Frameworks/MQTTnet.NetFramework/Properties/AssemblyInfo.cs deleted file mode 100644 index 4307dbcf..00000000 --- a/Frameworks/MQTTnet.NetFramework/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("MQTTnet")] -[assembly: AssemblyDescription("MQTTnet for .NET Framework")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Christian Kratky")] -[assembly: AssemblyProduct("MQTTnet")] -[assembly: AssemblyCopyright("Copyright © Christian Kratky 2016-2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] -[assembly: Guid("a480ef90-0eaa-4d9a-b271-47a9c47f6f7d")] -[assembly: AssemblyVersion("2.4.0.0")] -[assembly: AssemblyFileVersion("2.4.0.0")] \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs index 56650dde..a140ca0f 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs @@ -88,7 +88,11 @@ namespace MQTTnet.Implementations { try { +#if NET45 + var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false); +#else var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false); +#endif var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer()); ClientAccepted?.Invoke(clientAdapter); } @@ -108,7 +112,11 @@ namespace MQTTnet.Implementations { try { +#if NET45 + var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null).ConfigureAwait(false); +#else var clientSocket = await _tlsEndpointSocket.AcceptAsync().ConfigureAwait(false); +#endif var sslStream = new SslStream(new NetworkStream(clientSocket)); await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs index e60f641d..d94ddb4e 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs @@ -50,7 +50,11 @@ namespace MQTTnet.Implementations _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); } +#if NET45 + await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false); +#else await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false); +#endif if (_options.TlsOptions.UseTls) { diff --git a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj index 6e009895..a242439b 100644 --- a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj +++ b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj @@ -1,7 +1,7 @@  - netstandard1.3 + netstandard1.3;net45 MQTTnet MQTTnet 2.4.0.0 @@ -22,7 +22,7 @@ - + diff --git a/MQTTnet.sln b/MQTTnet.sln index 8ba4c60b..0afa5ec1 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.NetFramework", "Frameworks\MQTTnet.NetFramework\MQTTnet.NetFramework.csproj", "{A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.UniversalWindows", "Frameworks\MQTTnet.UniversalWindows\MQTTnet.UniversalWindows.csproj", "{BD60C727-D8E8-40C3-B8E3-C95A864AE611}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9248C2E1-B9D6-40BF-81EC-86004D7765B4}" @@ -15,8 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core", "MQTTnet.Core\MQTTnet.Core.csproj", "{2ECB99E4-72D0-4C23-99BA-93D511D3967D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp.NetFramework", "Tests\MQTTnet.TestApp.NetFramework\MQTTnet.TestApp.NetFramework.csproj", "{D9D74F33-6943-49B2-B765-7BD589082098}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp.UniversalWindows", "Tests\MQTTnet.TestApp.UniversalWindows\MQTTnet.TestApp.UniversalWindows.csproj", "{FF1F72D6-9524-4422-9497-3CC0002216ED}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.NetStandard", "Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}" @@ -62,22 +58,6 @@ Global {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.Build.0 = Release|Any CPU {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.ActiveCfg = Release|Any CPU {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.Build.0 = Release|Any CPU - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|ARM.ActiveCfg = Debug|Any CPU - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|ARM.Build.0 = Debug|Any CPU - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|x64.ActiveCfg = Debug|x64 - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|x64.Build.0 = Debug|x64 - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|x86.ActiveCfg = Debug|x86 - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|x86.Build.0 = Debug|x86 - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|Any CPU.Build.0 = Release|Any CPU - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|ARM.ActiveCfg = Release|Any CPU - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|ARM.Build.0 = Release|Any CPU - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|x64.ActiveCfg = Release|x64 - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|x64.Build.0 = Release|x64 - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|x86.ActiveCfg = Release|x86 - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|x86.Build.0 = Release|x86 {BD60C727-D8E8-40C3-B8E3-C95A864AE611}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BD60C727-D8E8-40C3-B8E3-C95A864AE611}.Debug|Any CPU.Build.0 = Debug|Any CPU {BD60C727-D8E8-40C3-B8E3-C95A864AE611}.Debug|ARM.ActiveCfg = Debug|ARM @@ -110,22 +90,6 @@ Global {2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x64.Build.0 = Release|Any CPU {2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x86.ActiveCfg = Release|Any CPU {2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x86.Build.0 = Release|Any CPU - {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|ARM.ActiveCfg = Debug|Any CPU - {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|ARM.Build.0 = Debug|Any CPU - {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|x64.ActiveCfg = Debug|x64 - {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|x64.Build.0 = Debug|x64 - {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|x86.ActiveCfg = Debug|x86 - {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|x86.Build.0 = Debug|x86 - {D9D74F33-6943-49B2-B765-7BD589082098}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D9D74F33-6943-49B2-B765-7BD589082098}.Release|Any CPU.Build.0 = Release|Any CPU - {D9D74F33-6943-49B2-B765-7BD589082098}.Release|ARM.ActiveCfg = Release|Any CPU - {D9D74F33-6943-49B2-B765-7BD589082098}.Release|ARM.Build.0 = Release|Any CPU - {D9D74F33-6943-49B2-B765-7BD589082098}.Release|x64.ActiveCfg = Release|x64 - {D9D74F33-6943-49B2-B765-7BD589082098}.Release|x64.Build.0 = Release|x64 - {D9D74F33-6943-49B2-B765-7BD589082098}.Release|x86.ActiveCfg = Release|x86 - {D9D74F33-6943-49B2-B765-7BD589082098}.Release|x86.Build.0 = Release|x86 {FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.ActiveCfg = Debug|ARM @@ -186,9 +150,7 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} - {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D} = {32A630A7-2598-41D7-B625-204CD906F5FB} {BD60C727-D8E8-40C3-B8E3-C95A864AE611} = {32A630A7-2598-41D7-B625-204CD906F5FB} - {D9D74F33-6943-49B2-B765-7BD589082098} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {FF1F72D6-9524-4422-9497-3CC0002216ED} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {3587E506-55A2-4EB3-99C7-DC01E42D25D2} = {32A630A7-2598-41D7-B625-204CD906F5FB} {3D283AAD-AAA8-4339-8394-52F80B6304DB} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} diff --git a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj index 5871b1e8..867b0132 100644 --- a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj +++ b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj @@ -3,7 +3,7 @@ Exe Full - netcoreapp2.0 + netcoreapp2.0;net45 diff --git a/Tests/MQTTnet.TestApp.NetFramework/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs similarity index 99% rename from Tests/MQTTnet.TestApp.NetFramework/PerformanceTest.cs rename to Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index 9f44419e..d7060f20 100644 --- a/Tests/MQTTnet.TestApp.NetFramework/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -11,7 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -namespace MQTTnet.TestApp.NetFramework +namespace MQTTnet.TestApp.NetCore { public static class PerformanceTest { diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index e89c42a0..9519f430 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -19,6 +19,7 @@ namespace MQTTnet.TestApp.NetCore Console.WriteLine("MQTTnet - TestApp.NetFramework"); Console.WriteLine("1 = Start client"); Console.WriteLine("2 = Start server"); + Console.WriteLine("3 = Start performance test"); var pressedKey = Console.ReadKey(true); if (pressedKey.Key == ConsoleKey.D1) { @@ -30,6 +31,11 @@ namespace MQTTnet.TestApp.NetCore Task.Run(() => RunServerAsync(args)); Thread.Sleep(Timeout.Infinite); } + else if (pressedKey.Key == ConsoleKey.D3) + { + Task.Run(PerformanceTest.RunAsync); + Thread.Sleep(Timeout.Infinite); + } } private static async Task RunClientAsync(string[] arguments) diff --git a/Tests/MQTTnet.TestApp.NetFramework/App.config b/Tests/MQTTnet.TestApp.NetFramework/App.config deleted file mode 100644 index 88fa4027..00000000 --- a/Tests/MQTTnet.TestApp.NetFramework/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.NetFramework/MQTTnet.TestApp.NetFramework.csproj b/Tests/MQTTnet.TestApp.NetFramework/MQTTnet.TestApp.NetFramework.csproj deleted file mode 100644 index 7d112f5a..00000000 --- a/Tests/MQTTnet.TestApp.NetFramework/MQTTnet.TestApp.NetFramework.csproj +++ /dev/null @@ -1,103 +0,0 @@ - - - - - Debug - AnyCPU - {D9D74F33-6943-49B2-B765-7BD589082098} - Exe - MQTTnet.TestApp.NetFramework - MQTTnet.TestApp.NetFramework - v4.5.2 - 512 - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - - - - - - - - - - - - - - - - - - - - {a480ef90-0eaa-4d9a-b271-47a9c47f6f7d} - MQTTnet.NetFramework - - - {2ecb99e4-72d0-4c23-99ba-93d511d3967d} - MQTTnet.Core - - - - \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.NetFramework/Program.cs b/Tests/MQTTnet.TestApp.NetFramework/Program.cs deleted file mode 100644 index 606aafaa..00000000 --- a/Tests/MQTTnet.TestApp.NetFramework/Program.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Core; -using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Packets; -using MQTTnet.Core.Protocol; -using MQTTnet.Core.Server; -using MQTTnet.Implementations; - -namespace MQTTnet.TestApp.NetFramework -{ - public static class Program - { - public static void Main(string[] args) - { - Console.WriteLine("MQTTnet - TestApp.NetFramework"); - Console.WriteLine("1 = Start client"); - Console.WriteLine("2 = Start server"); - Console.WriteLine("3 = Start performance test"); - var pressedKey = Console.ReadKey(true); - if (pressedKey.Key == ConsoleKey.D1) - { - Task.Run(() => RunClientAsync(args)); - Thread.Sleep(Timeout.Infinite); - } - else if (pressedKey.Key == ConsoleKey.D2) - { - Task.Run(() => RunServerAsync(args)); - Thread.Sleep(Timeout.Infinite); - } - else if (pressedKey.Key == ConsoleKey.D3) - { - Task.Run(PerformanceTest.RunAsync); - Thread.Sleep(Timeout.Infinite); - } - } - - private static async Task RunClientAsync(string[] arguments) - { - - MqttNetTrace.TraceMessagePublished += (s, e) => - { - Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); - if (e.Exception != null) - { - Console.WriteLine(e.Exception); - } - }; - - try - { - var options = new MqttClientWebSocketOptions - { - Uri = "broker.hivemq.com:8000/mqtt" - }; - - ////var options = new MqttClientOptions - ////{ - //// Server = "localhost", - //// ClientId = "XYZ", - //// CleanSession = true - ////}; - - var mqttClient = new MqttClientFactory().CreateMqttClient(); - mqttClient.ApplicationMessageReceived += (s, e) => - { - Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); - Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); - Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}"); - Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); - Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); - Console.WriteLine(); - }; - - mqttClient.Connected += async (s, e) => - { - Console.WriteLine("### CONNECTED WITH SERVER ###"); - - await mqttClient.SubscribeAsync(new List - { - new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce) - }); - }; - - mqttClient.Disconnected += async (s, e) => - { - Console.WriteLine("### DISCONNECTED FROM SERVER ###"); - await Task.Delay(TimeSpan.FromSeconds(5)); - - try - { - await mqttClient.ConnectAsync(options); - } - catch - { - Console.WriteLine("### RECONNECTING FAILED ###"); - } - }; - - try - { - await mqttClient.ConnectAsync(options); - } - catch (Exception exception) - { - Console.WriteLine("### CONNECTING FAILED ###" + Environment.NewLine + exception); - } - - Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###"); - - var messageFactory = new MqttApplicationMessageFactory(); - while (true) - { - Console.ReadLine(); - - var applicationMessage = messageFactory.CreateApplicationMessage("myTopic", "Hello World", MqttQualityOfServiceLevel.AtLeastOnce); - await mqttClient.PublishAsync(applicationMessage); - } - } - catch (Exception exception) - { - Console.WriteLine(exception); - } - } - - private static async Task RunServerAsync(string[] arguments) - { - MqttNetTrace.TraceMessagePublished += (s, e) => - { - Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); - if (e.Exception != null) - { - Console.WriteLine(e.Exception); - } - }; - - try - { - var options = new MqttServerOptions - { - ConnectionValidator = p => - { - if (p.ClientId == "SpecialClient") - { - if (p.Username != "USER" || p.Password != "PASS") - { - return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; - } - } - - return MqttConnectReturnCode.ConnectionAccepted; - } - }; - - var mqttServer = new MqttServerFactory().CreateMqttServer(options); - await mqttServer.StartAsync(); - - Console.WriteLine("Press any key to exit."); - Console.ReadLine(); - - await mqttServer.StopAsync(); - } - catch (Exception e) - { - Console.WriteLine(e); - } - - Console.ReadLine(); - } - - private static async Task WikiCode() - { - // For .NET Framwork & netstandard apps: - MqttTcpChannel.CustomCertificateValidationCallback = (x509Certificate, x509Chain, sslPolicyErrors, mqttClientTcpOptions) => - { - if (mqttClientTcpOptions.Server == "server_with_revoked_cert") - { - return true; - } - - return false; - }; - } - } -} \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.NetFramework/Properties/AssemblyInfo.cs b/Tests/MQTTnet.TestApp.NetFramework/Properties/AssemblyInfo.cs deleted file mode 100644 index 5fb54951..00000000 --- a/Tests/MQTTnet.TestApp.NetFramework/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("MQTTnet.TestApp.NetFramework")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Christian Kratky")] -[assembly: AssemblyProduct("MQTTnet")] -[assembly: AssemblyCopyright("Copyright © Christian Kratky 2016-2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] -[assembly: Guid("7b19b139-2e9d-4f1d-88b4-6180b4cf872a")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file From 47257baba9308789e420127cb5574feb900d11e3 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 14 Oct 2017 16:37:42 +0200 Subject: [PATCH 02/34] fixed build script --- Build/build.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Build/build.ps1 b/Build/build.ps1 index b8e71b13..70b616b9 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -3,8 +3,7 @@ param([string]$version) if ([string]::IsNullOrEmpty($version)) {$version = "0.0.1"} $msbuild = "MSBuild.exe" -&$msbuild ..\Frameworks\MQTTnet.NetFramework\MQTTnet.NetFramework.csproj /t:Build /p:Configuration="Release" -&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" +&dotnet build ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj -c="Release" &$msbuild ..\Frameworks\MQTTnet.UniversalWindows\MQTTnet.UniversalWindows.csproj /t:Build /p:Configuration="Release" Remove-Item .\NuGet -Force -Recurse From a2971aa03b357490612c4bd298d37857ff8e780a Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 15 Oct 2017 01:12:09 +0200 Subject: [PATCH 03/34] Update version information --- Build/MQTTnet.nuspec | 14 ++------------ .../MQTTnet.NetStandard/MQTTnet.Netstandard.csproj | 4 ++-- .../Properties/AssemblyInfo.cs | 4 ++-- MQTTnet.Core/MQTTnet.Core.csproj | 4 ++-- Tests/MQTTnet.TestApp.NetCore/Program.cs | 1 - 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index c7a9b86d..ffc3d7f1 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -2,7 +2,7 @@ MQTTnet - 2.4.0 + 2.5.0 Christian Kratky Christian Kratky https://github.com/chkr1011/MQTTnet/blob/master/LICENSE @@ -10,17 +10,7 @@ https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png false MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). - * [Server] Added an event which is fired when a client has disconnected -* [Server] Added support for retained application messages -* [Server] Added support for saving and loading retained messages -* [Server] The client connection is now closed if sending of one pending application message has failed -* [Server] Fixed handling of _Dup_ flag (Thanks to haeberle) -* [Core] Optimized exception handling -* [Core] Mono is now also supported (Thanks to JTrotta) -* [Client] The options are now passed in _ConnectAsync_ (Breaking change! Read Wiki for examples) -* [Core] Trace class renamed to _MqttNetTrace_ (Breaking change!) -* [Client] Extended certificate validation options (Breaking change!) -* [Client] Added static certificate validation callback (NetFramework, NetStandard) / ignorable certificate errors (UniversalWindows) to _MqttTcpChannel_ + * [Core] Merged the .NET Framwork and netstandard projects (Thanks to @JanEggers) Copyright Christian Kratky 2016-2017 MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M diff --git a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj index a242439b..88f25147 100644 --- a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj +++ b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj @@ -4,8 +4,8 @@ netstandard1.3;net45 MQTTnet MQTTnet - 2.4.0.0 - 2.4.0.0 + 2.5.0.0 + 2.5.0.0 0.0.0.0 diff --git a/Frameworks/MQTTnet.UniversalWindows/Properties/AssemblyInfo.cs b/Frameworks/MQTTnet.UniversalWindows/Properties/AssemblyInfo.cs index 54988c72..57632880 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Properties/AssemblyInfo.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Properties/AssemblyInfo.cs @@ -10,5 +10,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("2.4.0.0")] -[assembly: AssemblyFileVersion("2.4.0.0")] \ No newline at end of file +[assembly: AssemblyVersion("2.5.0.0")] +[assembly: AssemblyFileVersion("2.5.0.0")] \ No newline at end of file diff --git a/MQTTnet.Core/MQTTnet.Core.csproj b/MQTTnet.Core/MQTTnet.Core.csproj index 6997edce..0e9b965c 100644 --- a/MQTTnet.Core/MQTTnet.Core.csproj +++ b/MQTTnet.Core/MQTTnet.Core.csproj @@ -17,8 +17,8 @@ - 2.4.0.0 - 2.4.0.0 + 2.5.0.0 + 2.5.0.0 diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 9519f430..821d8610 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -40,7 +40,6 @@ namespace MQTTnet.TestApp.NetCore private static async Task RunClientAsync(string[] arguments) { - MqttNetTrace.TraceMessagePublished += (s, e) => { Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); From 60bdca8716aeb5c190dfcb3abea6a6bff4b76ba9 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sun, 15 Oct 2017 10:16:26 +0200 Subject: [PATCH 04/34] added back buffering for .net and annotated ifdefs with minimum dependency tor remove them --- .../Implementations/MqttServerAdapter.cs | 1 + .../Implementations/MqttTcpChannel.cs | 42 +++++++++++++++---- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs index a140ca0f..aa38653f 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs @@ -88,6 +88,7 @@ namespace MQTTnet.Implementations { try { +//todo: else branch can be used with min dependency NET46 #if NET45 var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false); #else diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs index d94ddb4e..dfe36540 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs @@ -15,6 +15,13 @@ namespace MQTTnet.Implementations { private readonly MqttClientTcpOptions _options; +//todo: this can be used with min dependency NetStandard1.6 +#if NET45 + // 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. +#endif + private Socket _socket; private SslStream _sslStream; @@ -34,12 +41,14 @@ namespace MQTTnet.Implementations { _socket = socket ?? throw new ArgumentNullException(nameof(socket)); _sslStream = sslStream; - ReceiveStream = (Stream)sslStream ?? new NetworkStream(socket); + + CreateStreams(socket, sslStream); } - public Stream SendStream => ReceiveStream; + public Stream SendStream { get; private set; } public Stream ReceiveStream { get; private set; } - public Stream RawReceiveStream => ReceiveStream; + public Stream RawReceiveStream { get; private set; } + public static Func CustomCertificateValidationCallback { get; set; } @@ -50,6 +59,7 @@ namespace MQTTnet.Implementations _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); } +//todo: else brach can be used with min dependency NET46 #if NET45 await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false); #else @@ -59,13 +69,10 @@ namespace MQTTnet.Implementations if (_options.TlsOptions.UseTls) { _sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); - ReceiveStream = _sslStream; await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); } - else - { - ReceiveStream = new NetworkStream(_socket); - } + + CreateStreams(_socket, _sslStream); } public Task DisconnectAsync() @@ -129,5 +136,24 @@ namespace MQTTnet.Implementations return certificates; } + + private void CreateStreams(Socket socket, Stream sslStream) + { + RawReceiveStream = sslStream ?? new NetworkStream(socket); + + + //cannot use this as default buffering prevents from receiving the first connect message + //need two streams otherwise read and write have to be synchronized + +//todo: if branch can be used with min dependency NetStandard1.6 +#if NET45 + SendStream = new BufferedStream(RawReceiveStream, BufferSize); + ReceiveStream = new BufferedStream(RawReceiveStream, BufferSize); +#else + SendStream = RawReceiveStream; + ReceiveStream = RawReceiveStream; +#endif + } + } } \ No newline at end of file From 1f8a781dc24708d73e00fce76d5925dfb7d3bb12 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 15 Oct 2017 12:00:46 +0200 Subject: [PATCH 05/34] Several refactorings & code cleanup --- .../Serializer/MqttPacketSerializer.cs | 189 +++++++----------- 1 file changed, 77 insertions(+), 112 deletions(-) diff --git a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs b/MQTTnet.Core/Serializer/MqttPacketSerializer.cs index 7f070d6b..d31a8fe7 100644 --- a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs +++ b/MQTTnet.Core/Serializer/MqttPacketSerializer.cs @@ -36,6 +36,16 @@ namespace MQTTnet.Core.Serializer } } + public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) + { + if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket)); + + using (var reader = new MqttPacketReader(receivedMqttPacket)) + { + return Deserialize(receivedMqttPacket.Header, reader); + } + } + private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter writer) { if (packet is MqttConnectPacket connectPacket) @@ -50,17 +60,17 @@ namespace MQTTnet.Core.Serializer if (packet is MqttDisconnectPacket disconnectPacket) { - return Serialize(disconnectPacket, writer); + return Serialize(disconnectPacket); } if (packet is MqttPingReqPacket pingReqPacket) { - return Serialize(pingReqPacket, writer); + return Serialize(pingReqPacket); } if (packet is MqttPingRespPacket pingRespPacket) { - return Serialize(pingRespPacket, writer); + return Serialize(pingRespPacket); } if (packet is MqttPublishPacket publishPacket) @@ -111,112 +121,68 @@ namespace MQTTnet.Core.Serializer throw new MqttProtocolViolationException("Packet type invalid."); } - public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) - { - if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket)); - - using (var reader = new MqttPacketReader(receivedMqttPacket)) - { - return Deserialize(receivedMqttPacket.Header, reader); - } - } - private static MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader) { switch (header.ControlPacketType) { - case MqttControlPacketType.Connect: - { - return DeserializeConnect(reader); - } - - case MqttControlPacketType.ConnAck: - { - return DeserializeConnAck(reader); - } - - case MqttControlPacketType.Disconnect: - { - return new MqttDisconnectPacket(); - } - - case MqttControlPacketType.Publish: - { - return DeserializePublish(reader, header); - } - - case MqttControlPacketType.PubAck: - { - return new MqttPubAckPacket - { - PacketIdentifier = reader.ReadUInt16() - }; - } - - case MqttControlPacketType.PubRec: - { - return new MqttPubRecPacket - { - PacketIdentifier = reader.ReadUInt16() - }; - } - - case MqttControlPacketType.PubRel: - { - return new MqttPubRelPacket - { - PacketIdentifier = reader.ReadUInt16() - }; - } - - case MqttControlPacketType.PubComp: - { - return new MqttPubCompPacket - { - PacketIdentifier = reader.ReadUInt16() - }; - } - - case MqttControlPacketType.PingReq: - { - return new MqttPingReqPacket(); - } - - case MqttControlPacketType.PingResp: - { - return new MqttPingRespPacket(); - } - - case MqttControlPacketType.Subscribe: - { - return DeserializeSubscribe(reader); - } - - case MqttControlPacketType.SubAck: - { - return DeserializeSubAck(reader); - } - - case MqttControlPacketType.Unsubscibe: - { - return DeserializeUnsubscribe(reader); - } - - case MqttControlPacketType.UnsubAck: - { - return new MqttUnsubAckPacket - { - PacketIdentifier = reader.ReadUInt16() - }; - } - - default: - { - throw new MqttProtocolViolationException($"Packet type ({(int)header.ControlPacketType}) not supported."); - } + case MqttControlPacketType.Connect: return DeserializeConnect(reader); + case MqttControlPacketType.ConnAck: return DeserializeConnAck(reader); + case MqttControlPacketType.Disconnect: return new MqttDisconnectPacket(); + case MqttControlPacketType.Publish: return DeserializePublish(reader, header); + case MqttControlPacketType.PubAck: return DeserializePubAck(reader); + case MqttControlPacketType.PubRec: return DeserializePubRec(reader); + case MqttControlPacketType.PubRel: return DeserializePubRel(reader); + case MqttControlPacketType.PubComp: return DeserializePubComp(reader); + case MqttControlPacketType.PingReq: return new MqttPingReqPacket(); + case MqttControlPacketType.PingResp: return new MqttPingRespPacket(); + case MqttControlPacketType.Subscribe: return DeserializeSubscribe(reader); + case MqttControlPacketType.SubAck: return DeserializeSubAck(reader); + case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(reader); + case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(reader); + default: throw new MqttProtocolViolationException($"Packet type ({(int)header.ControlPacketType}) not supported."); } } + private static MqttBasePacket DeserializeUnsubAck(MqttPacketReader reader) + { + return new MqttUnsubAckPacket + { + PacketIdentifier = reader.ReadUInt16() + }; + } + + private static MqttBasePacket DeserializePubComp(MqttPacketReader reader) + { + return new MqttPubCompPacket + { + PacketIdentifier = reader.ReadUInt16() + }; + } + + private static MqttBasePacket DeserializePubRel(MqttPacketReader reader) + { + return new MqttPubRelPacket + { + PacketIdentifier = reader.ReadUInt16() + }; + } + + private static MqttBasePacket DeserializePubRec(MqttPacketReader reader) + { + return new MqttPubRecPacket + { + PacketIdentifier = reader.ReadUInt16() + }; + } + + private static MqttBasePacket DeserializePubAck(MqttPacketReader reader) + { + return new MqttPubAckPacket + { + PacketIdentifier = reader.ReadUInt16() + }; + } + private static MqttBasePacket DeserializeUnsubscribe(MqttPacketReader reader) { var packet = new MqttUnsubscribePacket @@ -297,7 +263,7 @@ namespace MQTTnet.Core.Serializer throw new MqttProtocolViolationException("Protocol name is not supported."); } - var protocolLevel = reader.ReadByte(); + reader.ReadByte(); // Skip protocol level var connectFlags = reader.ReadByte(); var connectFlagsReader = new ByteReader(connectFlags); @@ -467,19 +433,19 @@ namespace MQTTnet.Core.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02); } - private static byte Serialize(MqttDisconnectPacket packet, MqttPacketWriter writer) + private static byte Serialize(MqttDisconnectPacket packet) { - return SerializeEmptyPacketAsync(MqttControlPacketType.Disconnect, writer); + return SerializeEmptyPacket(MqttControlPacketType.Disconnect); } - private static byte Serialize(MqttPingReqPacket packet, MqttPacketWriter writer) + private static byte Serialize(MqttPingReqPacket packet) { - return SerializeEmptyPacketAsync(MqttControlPacketType.PingReq, writer); + return SerializeEmptyPacket(MqttControlPacketType.PingReq); } - private static byte Serialize(MqttPingRespPacket packet, MqttPacketWriter writer) + private static byte Serialize(MqttPingRespPacket packet) { - return SerializeEmptyPacketAsync(MqttControlPacketType.PingResp, writer); + return SerializeEmptyPacket(MqttControlPacketType.PingResp); } private static byte Serialize(MqttPublishPacket packet, MqttPacketWriter writer) @@ -593,14 +559,13 @@ namespace MQTTnet.Core.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02); } - private static byte Serialize(MqttUnsubAckPacket packet, MqttPacketWriter writer) + private static byte Serialize(IMqttPacketWithIdentifier packet, BinaryWriter writer) { writer.Write(packet.PacketIdentifier); - return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck); } - private static byte SerializeEmptyPacketAsync(MqttControlPacketType type, MqttPacketWriter writer) + private static byte SerializeEmptyPacket(MqttControlPacketType type) { return MqttPacketWriter.BuildFixedHeader(type); } From ea5616002a8a68eccebf4c24dd8f79e26166c6b9 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Tue, 17 Oct 2017 14:02:41 +0200 Subject: [PATCH 06/34] Add support to not persistent Queue Added new class MqttClientQueued --- .../MQTTnet.NetStandard/MqttClientFactory.cs | 5 + MQTTnet.Core/Client/MqttClientQueued.cs | 530 ++++++++++++++++++ MQTTnet.Core/MQTTnet.Core.csproj | 6 + Tests/MQTTnet.TestApp.NetCore/Program.cs | 111 +++- 4 files changed, 647 insertions(+), 5 deletions(-) create mode 100644 MQTTnet.Core/Client/MqttClientQueued.cs diff --git a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs index 15da033d..93583907 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs @@ -9,5 +9,10 @@ namespace MQTTnet { return new MqttClient(new MqttCommunicationAdapterFactory()); } + + public IMqttClient CreateMqttQueuedClient() + { + return new MqttClientQueued(new MqttCommunicationAdapterFactory()); + } } } \ No newline at end of file diff --git a/MQTTnet.Core/Client/MqttClientQueued.cs b/MQTTnet.Core/Client/MqttClientQueued.cs new file mode 100644 index 00000000..925b9656 --- /dev/null +++ b/MQTTnet.Core/Client/MqttClientQueued.cs @@ -0,0 +1,530 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Core.Adapter; +using MQTTnet.Core.Diagnostics; +using MQTTnet.Core.Exceptions; +using MQTTnet.Core.Packets; +using MQTTnet.Core.Protocol; +using MQTTnet.Core.Internal; + +namespace MQTTnet.Core.Client +{ + public class MqttClientQueued : IMqttClient + { + #region Fields + private readonly IMqttCommunicationAdapterFactory _communicationChannelFactory; + private readonly MqttPacketDispatcher _packetDispatcher; + private readonly HashSet _unacknowledgedPublishPackets; + + private MqttClientOptions _options; + private bool _isReceivingPackets; + private int _latestPacketIdentifier; + private CancellationTokenSource _cancellationTokenSource; + private IMqttCommunicationAdapter _adapter; + + //added + private readonly ConcurrentQueue _inflightQueue; + private bool _usePersistance = false; + private ConcurrentQueue _persistentQueue; + #endregion + + #region Ctrs + public MqttClientQueued(IMqttCommunicationAdapterFactory communicationChannelFactory) + { + _communicationChannelFactory = communicationChannelFactory ?? throw new ArgumentNullException(nameof(communicationChannelFactory)); + _packetDispatcher = new MqttPacketDispatcher(); + _unacknowledgedPublishPackets = new HashSet(); + _inflightQueue = new ConcurrentQueue(); + _persistentQueue = new ConcurrentQueue(); + } + #endregion + + #region Events + public event EventHandler Connected; + public event EventHandler Disconnected; + public event EventHandler ApplicationMessageReceived; + #endregion + + #region Poperties + public bool IsConnected => _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested; + #endregion + + #region MqttClient Methods + + public async Task ConnectAsync(MqttClientOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + ThrowIfConnected("It is not allowed to connect with a server after the connection is established."); + + try + { + _options = options; + _cancellationTokenSource = new CancellationTokenSource(); + _latestPacketIdentifier = 0; + _packetDispatcher.Reset(); + + _adapter = _communicationChannelFactory.CreateMqttCommunicationAdapter(options); + + MqttNetTrace.Verbose(nameof(MqttClient), "Trying to connect with server."); + await _adapter.ConnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); + MqttNetTrace.Verbose(nameof(MqttClient), "Connection with server established."); + + await SetupIncomingPacketProcessingAsync(); + SetupOutgoingPacketProcessingAsync(); + await AuthenticateAsync(options.WillMessage); + + MqttNetTrace.Verbose(nameof(MqttClient), "MQTT connection with server established."); + + if (_options.KeepAlivePeriod != TimeSpan.Zero) + { + StartSendKeepAliveMessages(_cancellationTokenSource.Token); + } + + Connected?.Invoke(this, EventArgs.Empty); + } + catch (Exception) + { + await DisconnectInternalAsync().ConfigureAwait(false); + throw; + } + } + + public async Task DisconnectAsync() + { + if (!IsConnected) + { + return; + } + + try + { + await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false); + } + finally + { + await DisconnectInternalAsync().ConfigureAwait(false); + } + } + + public async Task PublishAsync(IEnumerable applicationMessages) + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + ThrowIfNotConnected(); + + foreach (var applicationMessage in applicationMessages) + { + if (_usePersistance) + _persistentQueue.Enqueue(applicationMessage); + _inflightQueue.Enqueue(applicationMessage); + } +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + + public async Task> SubscribeAsync(IEnumerable topicFilters) + { + if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); + + ThrowIfNotConnected(); + + var subscribePacket = new MqttSubscribePacket + { + PacketIdentifier = GetNewPacketIdentifier(), + TopicFilters = topicFilters.ToList() + }; + + var response = await SendAndReceiveAsync(subscribePacket).ConfigureAwait(false); + + if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count) + { + throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1]."); + } + + return subscribePacket.TopicFilters.Select((t, i) => new MqttSubscribeResult(t, response.SubscribeReturnCodes[i])).ToList(); + } + + public async Task UnsubscribeAsync(IEnumerable topicFilters) + { + if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); + + ThrowIfNotConnected(); + + var unsubscribePacket = new MqttUnsubscribePacket + { + PacketIdentifier = GetNewPacketIdentifier(), + TopicFilters = topicFilters.ToList() + }; + + await SendAndReceiveAsync(unsubscribePacket); + } + + #region private + private void ThrowIfNotConnected() + { + if (!IsConnected) throw new MqttCommunicationException("The client is not connected."); + } + + private void ThrowIfConnected(string message) + { + if (IsConnected) throw new MqttProtocolViolationException(message); + } + + private async Task SetupIncomingPacketProcessingAsync() + { + _isReceivingPackets = false; + +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Task.Factory.StartNew( + () => ReceivePackets(_cancellationTokenSource.Token), + _cancellationTokenSource.Token, + TaskCreationOptions.LongRunning, + TaskScheduler.Default).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 && _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested) + { + await Task.Delay(TimeSpan.FromMilliseconds(100)); + } + } + + private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage) + { + var connectPacket = new MqttConnectPacket + { + ClientId = _options.ClientId, + Username = _options.UserName, + Password = _options.Password, + CleanSession = _options.CleanSession, + KeepAlivePeriod = (ushort)_options.KeepAlivePeriod.TotalSeconds, + WillMessage = willApplicationMessage + }; + + var response = await SendAndReceiveAsync(connectPacket).ConfigureAwait(false); + if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted) + { + throw new MqttConnectingFailedException(response.ConnectReturnCode); + } + } + + private async Task ReceivePackets(CancellationToken cancellationToken) + { + MqttNetTrace.Information(nameof(MqttClient), "Start receiving packets."); + + try + { + while (!cancellationToken.IsCancellationRequested) + { + _isReceivingPackets = true; + + var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); + if (cancellationToken.IsCancellationRequested) + { + return; + } + + StartProcessReceivedPacket(packet, cancellationToken); + } + } + catch (OperationCanceledException) + { + } + catch (MqttCommunicationException exception) + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + + MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets."); + await DisconnectInternalAsync().ConfigureAwait(false); + } + catch (Exception exception) + { + MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets."); + await DisconnectInternalAsync().ConfigureAwait(false); + } + finally + { + MqttNetTrace.Information(nameof(MqttClient), "Stopped receiving packets."); + } + } + + private void StartProcessReceivedPacket(MqttBasePacket packet, CancellationToken cancellationToken) + { +#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), 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 ProcessReceivedPacketAsync(MqttBasePacket packet) + { + try + { + MqttNetTrace.Information(nameof(MqttClient), "Received <<< {0}", packet); + + if (packet is MqttPingReqPacket) + { + await SendAsync(new MqttPingRespPacket()); + return; + } + + if (packet is MqttDisconnectPacket) + { + await DisconnectAsync(); + return; + } + + if (packet is MqttPublishPacket publishPacket) + { + await ProcessReceivedPublishPacket(publishPacket); + return; + } + + if (packet is MqttPubRelPacket pubRelPacket) + { + await ProcessReceivedPubRelPacket(pubRelPacket); + return; + } + + _packetDispatcher.Dispatch(packet); + } + catch (Exception exception) + { + MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while processing received packet."); + } + } + + private async Task ProcessReceivedPublishPacket(MqttPublishPacket publishPacket) + { + if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) + { + FireApplicationMessageReceivedEvent(publishPacket); + return; + } + + if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) + { + FireApplicationMessageReceivedEvent(publishPacket); + await SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }); + return; + } + + if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) + { + // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] + lock (_unacknowledgedPublishPackets) + { + _unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier); + } + + FireApplicationMessageReceivedEvent(publishPacket); + await SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }); + return; + } + + throw new MqttCommunicationException("Received a not supported QoS level."); + } + + private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket) + { + lock (_unacknowledgedPublishPackets) + { + _unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier); + } + + return SendAsync(pubRelPacket.CreateResponse()); + } + + private async Task DisconnectInternalAsync() + { + var cts = _cancellationTokenSource; + if (cts == null || cts.IsCancellationRequested) + { + return; + } + + cts.Cancel(false); + cts.Dispose(); + _cancellationTokenSource = null; + + try + { + await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); + MqttNetTrace.Information(nameof(MqttClient), "Disconnected from adapter."); + } + catch (Exception exception) + { + MqttNetTrace.Warning(nameof(MqttClient), exception, "Error while disconnecting from adapter."); + } + finally + { + Disconnected?.Invoke(this, EventArgs.Empty); + } + } + + private void StartSendKeepAliveMessages(CancellationToken cancellationToken) + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Task.Factory.StartNew(() => SendKeepAliveMessagesAsync(cancellationToken), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).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 SendKeepAliveMessagesAsync(CancellationToken cancellationToken) + { + MqttNetTrace.Information(nameof(MqttClient), "Start sending keep alive packets."); + + try + { + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(_options.KeepAlivePeriod, cancellationToken).ConfigureAwait(false); + if (cancellationToken.IsCancellationRequested) + { + return; + } + + await SendAndReceiveAsync(new MqttPingReqPacket()).ConfigureAwait(false); + } + } + catch (OperationCanceledException) + { + } + catch (MqttCommunicationException exception) + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + + MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending/receiving keep alive packets."); + await DisconnectInternalAsync().ConfigureAwait(false); + } + catch (Exception exception) + { + MqttNetTrace.Warning(nameof(MqttClient), exception, "Unhandled exception while sending/receiving keep alive packets."); + await DisconnectInternalAsync().ConfigureAwait(false); + } + finally + { + MqttNetTrace.Information(nameof(MqttClient), "Stopped sending keep alive packets."); + } + } + + private async Task SendAndReceiveAsync(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket + { + var packetAwaiter = _packetDispatcher.WaitForPacketAsync(requestPacket, typeof(TResponsePacket), _options.DefaultCommunicationTimeout); + await _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false); + return (TResponsePacket)await packetAwaiter.ConfigureAwait(false); + } + + private Task SendAsync(MqttBasePacket packet) + { + return _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, packet); + } + + private ushort GetNewPacketIdentifier() + { + return (ushort)Interlocked.Increment(ref _latestPacketIdentifier); + } + + private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket) + { + try + { + var applicationMessage = publishPacket.ToApplicationMessage(); + ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(applicationMessage)); + } + catch (Exception exception) + { + MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while handling application message."); + } + } + #endregion + #endregion + + #region MqttClientQueued Methos + private void SetupOutgoingPacketProcessingAsync() + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Task.Factory.StartNew( + () => SendPackets(_cancellationTokenSource.Token), + _cancellationTokenSource.Token, + TaskCreationOptions.LongRunning, + TaskScheduler.Default).ConfigureAwait(false); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + + //while (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested) + //{ + // await Task.Delay(TimeSpan.FromMilliseconds(100)); + //} + } + + private async Task SendPackets(CancellationToken cancellationToken) + { + MqttNetTrace.Information(nameof(MqttClient), "Start sending packets."); + MqttApplicationMessage messageToSend = null; + try + { + while (!cancellationToken.IsCancellationRequested) + { + while (_inflightQueue.TryDequeue(out messageToSend)) + { + MqttQualityOfServiceLevel qosGroup = messageToSend.QualityOfServiceLevel; + MqttPublishPacket publishPacket = messageToSend.ToPublishPacket(); + switch (qosGroup) + { + case MqttQualityOfServiceLevel.AtMostOnce: + { + // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] + await _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, publishPacket); + break; + } + + case MqttQualityOfServiceLevel.AtLeastOnce: + { + publishPacket.PacketIdentifier = GetNewPacketIdentifier(); + await SendAndReceiveAsync(publishPacket); + break; + } + case MqttQualityOfServiceLevel.ExactlyOnce: + { + publishPacket.PacketIdentifier = GetNewPacketIdentifier(); + var pubRecPacket = await SendAndReceiveAsync(publishPacket).ConfigureAwait(false); + await SendAndReceiveAsync(pubRecPacket.CreateResponse()).ConfigureAwait(false); + break; + } + default: + { + throw new InvalidOperationException(); + } + } + } + }; + } + catch (OperationCanceledException) + { + } + catch (MqttCommunicationException exception) + { + MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending packets."); + //message not send, equeued again + if (messageToSend != null) + _inflightQueue.Enqueue(messageToSend); + } + catch (Exception exception) + { + MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while sending packets."); + await DisconnectInternalAsync().ConfigureAwait(false); + } + finally + { + MqttNetTrace.Information(nameof(MqttClient), "Stopped sending packets."); + } + } + #endregion + } +} diff --git a/MQTTnet.Core/MQTTnet.Core.csproj b/MQTTnet.Core/MQTTnet.Core.csproj index 0e9b965c..1e507cb7 100644 --- a/MQTTnet.Core/MQTTnet.Core.csproj +++ b/MQTTnet.Core/MQTTnet.Core.csproj @@ -32,4 +32,10 @@ + + + + + + \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 821d8610..f2a44e0c 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -18,26 +18,127 @@ namespace MQTTnet.TestApp.NetCore { Console.WriteLine("MQTTnet - TestApp.NetFramework"); Console.WriteLine("1 = Start client"); - Console.WriteLine("2 = Start server"); - Console.WriteLine("3 = Start performance test"); + Console.WriteLine("2 = Start queued client"); + Console.WriteLine("3 = Start server"); + Console.WriteLine("4 = Start performance test"); var pressedKey = Console.ReadKey(true); if (pressedKey.Key == ConsoleKey.D1) { Task.Run(() => RunClientAsync(args)); Thread.Sleep(Timeout.Infinite); } - else if (pressedKey.Key == ConsoleKey.D2) + if (pressedKey.Key == ConsoleKey.D2) { - Task.Run(() => RunServerAsync(args)); + Task.Run(() => RunClientQueuedAsync(args)); Thread.Sleep(Timeout.Infinite); } else if (pressedKey.Key == ConsoleKey.D3) + { + Task.Run(() => RunServerAsync(args)); + Thread.Sleep(Timeout.Infinite); + } + else if (pressedKey.Key == ConsoleKey.D4) { Task.Run(PerformanceTest.RunAsync); Thread.Sleep(Timeout.Infinite); } } + private static async Task RunClientQueuedAsync(string[] arguments) + { + MqttNetTrace.TraceMessagePublished += (s, e) => + { + Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); + if (e.Exception != null) + { + Console.WriteLine(e.Exception); + } + }; + + try + { + var options = new MqttClientTcpOptions + { + Server = "192.168.0.14", + ClientId = "XYZ", + CleanSession = true, + UserName = "lobu", + Password = "passworda", + KeepAlivePeriod = TimeSpan.FromSeconds(31), + DefaultCommunicationTimeout = TimeSpan.FromSeconds(20), + + }; + + var client = new MqttClientFactory().CreateMqttQueuedClient(); + client.ApplicationMessageReceived += (s, e) => + { + Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); + Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); + Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}"); + Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); + Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); + Console.WriteLine(); + }; + + client.Connected += async (s, e) => + { + Console.WriteLine("### CONNECTED WITH SERVER ###"); + + await client.SubscribeAsync(new List + { + new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce) + }); + + Console.WriteLine("### SUBSCRIBED ###"); + }; + + client.Disconnected += async (s, e) => + { + Console.WriteLine("### DISCONNECTED FROM SERVER ###"); + await Task.Delay(TimeSpan.FromSeconds(5)); + + try + { + await client.ConnectAsync(options); + } + catch + { + Console.WriteLine("### RECONNECTING FAILED ###"); + } + }; + + try + { + await client.ConnectAsync(options); + } + catch (Exception exception) + { + Console.WriteLine("### CONNECTING FAILED ###" + Environment.NewLine + exception); + } + + Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###"); + + int i = 0; + while (true) + { + Console.ReadLine(); + i++; + var applicationMessage = new MqttApplicationMessage( + "PLNMAIN", + Encoding.UTF8.GetBytes(string.Format("Hello World {0}", i)), + MqttQualityOfServiceLevel.ExactlyOnce, + false + ); + + await client.PublishAsync(applicationMessage); + } + } + catch (Exception exception) + { + Console.WriteLine(exception); + } + } + private static async Task RunClientAsync(string[] arguments) { MqttNetTrace.TraceMessagePublished += (s, e) => @@ -55,7 +156,7 @@ namespace MQTTnet.TestApp.NetCore { Uri = "localhost", ClientId = "XYZ", - CleanSession = true + CleanSession = true, }; var client = new MqttClientFactory().CreateMqttClient(); From ad4585989a4701d673fb502ffadf97df086771c0 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 17 Oct 2017 20:04:05 +0200 Subject: [PATCH 07/34] Update documentation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b7594d8..2ded79f3 100644 --- a/README.md +++ b/README.md @@ -53,12 +53,12 @@ This library is available as a nuget package: https://www.nuget.org/packages/MQT Please find examples and the documentation at the Wiki of this repository (https://github.com/chkr1011/MQTTnet/wiki). # Contributions -If you want to contribute to this project just create a pull request. +If you want to contribute to this project just create a pull request. But only pull requests which are matching the code style of this library will be accepted. Before creating a pull request please have a look at the library to get an overview of the required style. # References This library is used in the following projects: * MQTT Client Rx (Wrapper for Reactive Extensions, https://github.com/1iveowl/MQTTClient.rx) -* HA4IoT (Open Source Home Automation system for .NET, https://github.com/chkr1011/HA4IoT) +* Wirehome (Open Source Home Automation system for .NET, https://github.com/chkr1011/Wirehome) If you use this library and want to see your project here please let me know. \ No newline at end of file From 9915cc4492df5a8c94b5f60f76aafe9cf3df1166 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Tue, 17 Oct 2017 20:05:04 +0200 Subject: [PATCH 08/34] Use event pattern correctly for incoming server connections. --- .../Implementations/MqttServerAdapter.cs | 22 +++++++++---------- .../Implementations/MqttServerAdapter.cs | 14 ++++-------- MQTTnet.Core/Adapter/IMqttServerAdapter.cs | 2 +- ...qttServerAdapterClientAcceptedEventArgs.cs | 14 ++++++++++++ .../MqttClientConnectedEventArgs.cs | 5 ++--- .../MqttClientDisconnectedEventArgs.cs | 5 ++--- MQTTnet.Core/Server/MqttServer.cs | 4 ++-- MQTTnet.sln | 2 +- .../TestMqttServerAdapter.cs | 4 ++-- Tests/MQTTnet.TestApp.NetCore/Program.cs | 6 ++++- 10 files changed, 44 insertions(+), 34 deletions(-) create mode 100644 MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs rename MQTTnet.Core/{Adapter => Server}/MqttClientConnectedEventArgs.cs (79%) rename MQTTnet.Core/{Adapter => Server}/MqttClientDisconnectedEventArgs.cs (80%) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs index aa38653f..b035d89d 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs @@ -20,15 +20,11 @@ namespace MQTTnet.Implementations private Socket _tlsEndpointSocket; private X509Certificate2 _tlsCertificate; - private bool _isRunning; - - public event Action ClientAccepted; + public event EventHandler ClientAccepted; public Task StartAsync(MqttServerOptions options) { - if (_isRunning) throw new InvalidOperationException("Server is already started."); - - _isRunning = true; + if (_cancellationTokenSource != null) throw new InvalidOperationException("Server is already started."); _cancellationTokenSource = new CancellationTokenSource(); @@ -49,6 +45,10 @@ namespace MQTTnet.Implementations } _tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate); + if (!_tlsCertificate.HasPrivateKey) + { + throw new InvalidOperationException("The certificate for TLS encryption must contain the private key."); + } _tlsEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); _tlsEndpointSocket.Bind(new IPEndPoint(IPAddress.Any, options.GetTlsEndpointPort())); @@ -62,8 +62,6 @@ namespace MQTTnet.Implementations public Task StopAsync() { - _isRunning = false; - _cancellationTokenSource?.Cancel(false); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; @@ -71,6 +69,8 @@ namespace MQTTnet.Implementations _defaultEndpointSocket?.Dispose(); _defaultEndpointSocket = null; + _tlsCertificate = null; + _tlsEndpointSocket?.Dispose(); _tlsEndpointSocket = null; @@ -88,14 +88,14 @@ namespace MQTTnet.Implementations { try { -//todo: else branch can be used with min dependency NET46 + //todo: else branch can be used with min dependency NET46 #if NET45 var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false); #else var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false); #endif var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer()); - ClientAccepted?.Invoke(clientAdapter); + ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) { @@ -123,7 +123,7 @@ namespace MQTTnet.Implementations await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer()); - ClientAccepted?.Invoke(clientAdapter); + ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) { diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs index 1a742cba..f33dd593 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs @@ -12,18 +12,14 @@ namespace MQTTnet.Implementations { private StreamSocketListener _defaultEndpointSocket; - private bool _isRunning; - - public event Action ClientAccepted; + public event EventHandler ClientAccepted; public async Task StartAsync(MqttServerOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); - if (_isRunning) throw new InvalidOperationException("Server is already started."); - - _isRunning = true; - + if (_defaultEndpointSocket != null) throw new InvalidOperationException("Server is already started."); + if (options.DefaultEndpointOptions.IsEnabled) { _defaultEndpointSocket = new StreamSocketListener(); @@ -39,8 +35,6 @@ namespace MQTTnet.Implementations public Task StopAsync() { - _isRunning = false; - _defaultEndpointSocket?.Dispose(); _defaultEndpointSocket = null; @@ -57,7 +51,7 @@ namespace MQTTnet.Implementations try { var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer()); - ClientAccepted?.Invoke(clientAdapter); + ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) { diff --git a/MQTTnet.Core/Adapter/IMqttServerAdapter.cs b/MQTTnet.Core/Adapter/IMqttServerAdapter.cs index b1546efd..9209185e 100644 --- a/MQTTnet.Core/Adapter/IMqttServerAdapter.cs +++ b/MQTTnet.Core/Adapter/IMqttServerAdapter.cs @@ -6,7 +6,7 @@ namespace MQTTnet.Core.Adapter { public interface IMqttServerAdapter { - event Action ClientAccepted; + event EventHandler ClientAccepted; Task StartAsync(MqttServerOptions options); Task StopAsync(); diff --git a/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs b/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs new file mode 100644 index 00000000..6d7fb54b --- /dev/null +++ b/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace MQTTnet.Core.Adapter +{ + public class MqttServerAdapterClientAcceptedEventArgs : EventArgs + { + public MqttServerAdapterClientAcceptedEventArgs(IMqttCommunicationAdapter client) + { + Client = client ?? throw new ArgumentNullException(nameof(client)); + } + + public IMqttCommunicationAdapter Client { get; } + } +} diff --git a/MQTTnet.Core/Adapter/MqttClientConnectedEventArgs.cs b/MQTTnet.Core/Server/MqttClientConnectedEventArgs.cs similarity index 79% rename from MQTTnet.Core/Adapter/MqttClientConnectedEventArgs.cs rename to MQTTnet.Core/Server/MqttClientConnectedEventArgs.cs index ade4d9d5..282e0f8d 100644 --- a/MQTTnet.Core/Adapter/MqttClientConnectedEventArgs.cs +++ b/MQTTnet.Core/Server/MqttClientConnectedEventArgs.cs @@ -1,7 +1,6 @@ -using MQTTnet.Core.Server; -using System; +using System; -namespace MQTTnet.Core.Adapter +namespace MQTTnet.Core.Server { public class MqttClientConnectedEventArgs : EventArgs { diff --git a/MQTTnet.Core/Adapter/MqttClientDisconnectedEventArgs.cs b/MQTTnet.Core/Server/MqttClientDisconnectedEventArgs.cs similarity index 80% rename from MQTTnet.Core/Adapter/MqttClientDisconnectedEventArgs.cs rename to MQTTnet.Core/Server/MqttClientDisconnectedEventArgs.cs index 8316d403..b1e3d15b 100644 --- a/MQTTnet.Core/Adapter/MqttClientDisconnectedEventArgs.cs +++ b/MQTTnet.Core/Server/MqttClientDisconnectedEventArgs.cs @@ -1,7 +1,6 @@ -using MQTTnet.Core.Server; -using System; +using System; -namespace MQTTnet.Core.Adapter +namespace MQTTnet.Core.Server { public class MqttClientDisconnectedEventArgs : EventArgs { diff --git a/MQTTnet.Core/Server/MqttServer.cs b/MQTTnet.Core/Server/MqttServer.cs index 2fa72057..d6f23793 100644 --- a/MQTTnet.Core/Server/MqttServer.cs +++ b/MQTTnet.Core/Server/MqttServer.cs @@ -77,9 +77,9 @@ namespace MQTTnet.Core.Server MqttNetTrace.Information(nameof(MqttServer), "Stopped."); } - private void OnClientAccepted(IMqttCommunicationAdapter adapter) + private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) { - Task.Run(() =>_clientSessionsManager.RunClientSessionAsync(adapter), _cancellationTokenSource.Token); + Task.Run(() =>_clientSessionsManager.RunClientSessionAsync(eventArgs.Client), _cancellationTokenSource.Token); } private void OnClientConnected(object sender, MqttClientConnectedEventArgs eventArgs) diff --git a/MQTTnet.sln b/MQTTnet.sln index 0afa5ec1..4d49d12f 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.16 +VisualStudioVersion = 15.0.27004.2002 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}" EndProject diff --git a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs index 19065646..6cfa9bbb 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs @@ -8,7 +8,7 @@ namespace MQTTnet.Core.Tests { public class TestMqttServerAdapter : IMqttServerAdapter { - public event Action ClientAccepted; + public event EventHandler ClientAccepted; public async Task ConnectTestClient(IMqttServer server, string clientId, MqttApplicationMessage willMessage = null) { @@ -47,7 +47,7 @@ namespace MQTTnet.Core.Tests private void FireClientAcceptedEvent(IMqttCommunicationAdapter adapter) { - ClientAccepted?.Invoke(adapter); + ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(adapter)); } public Task StartAsync(MqttServerOptions options) diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 821d8610..31d26dbb 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -6,6 +6,7 @@ using MQTTnet.Core.Protocol; using MQTTnet.Core.Server; using System; using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -42,7 +43,7 @@ namespace MQTTnet.TestApp.NetCore { MqttNetTrace.TraceMessagePublished += (s, e) => { - Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); + Console.WriteLine($">> [{DateTime.Now:O}] [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); if (e.Exception != null) { Console.WriteLine(e.Exception); @@ -156,6 +157,9 @@ namespace MQTTnet.TestApp.NetCore } }; + var certificate = new X509Certificate(@"C:\certs\test\test.cer", ""); + options.TlsEndpointOptions.Certificate = certificate.Export(X509ContentType.Cert); + var mqttServer = new MqttServerFactory().CreateMqttServer(options); mqttServer.StartAsync(); From bc19f66812ca54265793a3ca820c0de1d6c5e8c4 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Wed, 18 Oct 2017 16:41:54 +0200 Subject: [PATCH 09/34] Added suport to Persistence and refoactoring --- .../MqttCommunicationAdapterFactory.cs | 5 + .../MQTTnet.NetStandard/MqttClientFactory.cs | 2 +- .../MqttCommunicationAdapterFactory.cs | 2 +- .../Implementations/MqttTcpChannel.cs | 2 +- .../MqttClientFactory.cs | 5 + MQTTnet.Core/Client/IMqttClientFactory.cs | 2 + MQTTnet.Core/Client/IMqttClientQueued.cs | 9 + .../Client/IMqttClientQueuedStorage.cs | 12 + MQTTnet.Core/Client/MqttClient.cs | 6 +- MQTTnet.Core/Client/MqttClientQueued.cs | 432 ++---------------- .../Client/MqttClientQueuedOptions.cs | 11 + ...ttClientQueuedPersistentMessagesManager.cs | 84 ++++ MQTTnet.Core/MQTTnet.Core.csproj | 14 - .../MqttCommunicationAdapterFactory.cs | 2 +- Tests/MQTTnet.TestApp.NetCore/Program.cs | 71 ++- Tests/MQTTnet.TestApp.NetCore/messages.bin | Bin 0 -> 474 bytes .../MainPage.xaml.cs | 2 +- 17 files changed, 239 insertions(+), 422 deletions(-) create mode 100644 MQTTnet.Core/Client/IMqttClientQueued.cs create mode 100644 MQTTnet.Core/Client/IMqttClientQueuedStorage.cs create mode 100644 MQTTnet.Core/Client/MqttClientQueuedOptions.cs create mode 100644 MQTTnet.Core/Client/MqttClientQueuedPersistentMessagesManager.cs create mode 100644 Tests/MQTTnet.TestApp.NetCore/messages.bin diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs index 799ccddd..218bb8cf 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs @@ -21,6 +21,11 @@ namespace MQTTnet.Implementations return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); } + if (options is MqttClientQueuedOptions queuedOptions) + { + return new MqttChannelCommunicationAdapter(new MqttTcpChannel(queuedOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); + } + throw new NotSupportedException(); } } diff --git a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs index 93583907..5bc8336f 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs @@ -10,7 +10,7 @@ namespace MQTTnet return new MqttClient(new MqttCommunicationAdapterFactory()); } - public IMqttClient CreateMqttQueuedClient() + public IMqttClientQueued CreateMqttQueuedClient() { return new MqttClientQueued(new MqttCommunicationAdapterFactory()); } diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs index 799ccddd..2bb7c5ef 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs @@ -7,7 +7,7 @@ namespace MQTTnet.Implementations { public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory { - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options) + public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientQueuedOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs index 03206f1f..db102912 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs @@ -89,7 +89,7 @@ namespace MQTTnet.Implementations RawReceiveStream = ReceiveStream; } - private static Certificate LoadCertificate(MqttClientOptions options) + private static Certificate LoadCertificate(MqttClientQueuedOptions options) { if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any()) { diff --git a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs b/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs index 15da033d..5bc8336f 100644 --- a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs +++ b/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs @@ -9,5 +9,10 @@ namespace MQTTnet { return new MqttClient(new MqttCommunicationAdapterFactory()); } + + public IMqttClientQueued CreateMqttQueuedClient() + { + return new MqttClientQueued(new MqttCommunicationAdapterFactory()); + } } } \ No newline at end of file diff --git a/MQTTnet.Core/Client/IMqttClientFactory.cs b/MQTTnet.Core/Client/IMqttClientFactory.cs index 033eb993..f1d64114 100644 --- a/MQTTnet.Core/Client/IMqttClientFactory.cs +++ b/MQTTnet.Core/Client/IMqttClientFactory.cs @@ -3,5 +3,7 @@ public interface IMqttClientFactory { IMqttClient CreateMqttClient(); + + IMqttClientQueued CreateMqttQueuedClient(); } } \ No newline at end of file diff --git a/MQTTnet.Core/Client/IMqttClientQueued.cs b/MQTTnet.Core/Client/IMqttClientQueued.cs new file mode 100644 index 00000000..c172e0cc --- /dev/null +++ b/MQTTnet.Core/Client/IMqttClientQueued.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Core.Client +{ + public interface IMqttClientQueued: IMqttClient + { + Task ConnectAsync(MqttClientQueuedOptions options); + } +} diff --git a/MQTTnet.Core/Client/IMqttClientQueuedStorage.cs b/MQTTnet.Core/Client/IMqttClientQueuedStorage.cs new file mode 100644 index 00000000..ffc5e5f6 --- /dev/null +++ b/MQTTnet.Core/Client/IMqttClientQueuedStorage.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MQTTnet.Core.Client +{ + public interface IMqttClientQueuedStorage + { + Task SaveInflightMessagesAsync(IList messages); + + Task> LoadInflightMessagesAsync(); + } +} diff --git a/MQTTnet.Core/Client/MqttClient.cs b/MQTTnet.Core/Client/MqttClient.cs index 80b850a8..002ebe83 100644 --- a/MQTTnet.Core/Client/MqttClient.cs +++ b/MQTTnet.Core/Client/MqttClient.cs @@ -21,8 +21,8 @@ namespace MQTTnet.Core.Client private MqttClientOptions _options; private bool _isReceivingPackets; private int _latestPacketIdentifier; - private CancellationTokenSource _cancellationTokenSource; - private IMqttCommunicationAdapter _adapter; + internal CancellationTokenSource _cancellationTokenSource; + internal IMqttCommunicationAdapter _adapter; public MqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory) { @@ -344,7 +344,7 @@ namespace MQTTnet.Core.Client return _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, packet); } - private async Task SendAndReceiveAsync(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket + internal async Task SendAndReceiveAsync(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket { var packetAwaiter = _packetDispatcher.WaitForPacketAsync(requestPacket, typeof(TResponsePacket), _options.DefaultCommunicationTimeout); await _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false); diff --git a/MQTTnet.Core/Client/MqttClientQueued.cs b/MQTTnet.Core/Client/MqttClientQueued.cs index 925b9656..05d5766a 100644 --- a/MQTTnet.Core/Client/MqttClientQueued.cs +++ b/MQTTnet.Core/Client/MqttClientQueued.cs @@ -13,454 +13,88 @@ using MQTTnet.Core.Internal; namespace MQTTnet.Core.Client { - public class MqttClientQueued : IMqttClient + public class MqttClientQueued : MqttClient, IMqttClientQueued { - #region Fields - private readonly IMqttCommunicationAdapterFactory _communicationChannelFactory; - private readonly MqttPacketDispatcher _packetDispatcher; - private readonly HashSet _unacknowledgedPublishPackets; - - private MqttClientOptions _options; - private bool _isReceivingPackets; + private MqttClientQueuedOptions _options; private int _latestPacketIdentifier; - private CancellationTokenSource _cancellationTokenSource; - private IMqttCommunicationAdapter _adapter; - - //added private readonly ConcurrentQueue _inflightQueue; private bool _usePersistance = false; - private ConcurrentQueue _persistentQueue; - #endregion + private MqttClientQueuedPersistentMessagesManager _persistentMessagesManager; - #region Ctrs - public MqttClientQueued(IMqttCommunicationAdapterFactory communicationChannelFactory) + public MqttClientQueued(IMqttCommunicationAdapterFactory communicationChannelFactory) : base(communicationChannelFactory) { - _communicationChannelFactory = communicationChannelFactory ?? throw new ArgumentNullException(nameof(communicationChannelFactory)); - _packetDispatcher = new MqttPacketDispatcher(); - _unacknowledgedPublishPackets = new HashSet(); _inflightQueue = new ConcurrentQueue(); - _persistentQueue = new ConcurrentQueue(); } - #endregion - #region Events - public event EventHandler Connected; - public event EventHandler Disconnected; - public event EventHandler ApplicationMessageReceived; - #endregion - #region Poperties - public bool IsConnected => _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested; - #endregion - - #region MqttClient Methods - - public async Task ConnectAsync(MqttClientOptions options) + public async Task ConnectAsync(MqttClientQueuedOptions options) { - if (options == null) throw new ArgumentNullException(nameof(options)); - - ThrowIfConnected("It is not allowed to connect with a server after the connection is established."); - try - { + { _options = options; - _cancellationTokenSource = new CancellationTokenSource(); - _latestPacketIdentifier = 0; - _packetDispatcher.Reset(); - - _adapter = _communicationChannelFactory.CreateMqttCommunicationAdapter(options); - - MqttNetTrace.Verbose(nameof(MqttClient), "Trying to connect with server."); - await _adapter.ConnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); - MqttNetTrace.Verbose(nameof(MqttClient), "Connection with server established."); - - await SetupIncomingPacketProcessingAsync(); + this._usePersistance = _options.UsePersistence; + await base.ConnectAsync(options); SetupOutgoingPacketProcessingAsync(); - await AuthenticateAsync(options.WillMessage); - MqttNetTrace.Verbose(nameof(MqttClient), "MQTT connection with server established."); - - if (_options.KeepAlivePeriod != TimeSpan.Zero) + //load persistentMessages + if (_usePersistance) { - StartSendKeepAliveMessages(_cancellationTokenSource.Token); + if (_persistentMessagesManager == null) + _persistentMessagesManager = new MqttClientQueuedPersistentMessagesManager(_options); + await _persistentMessagesManager.LoadMessagesAsync(); + await InternalPublishAsync(_persistentMessagesManager.GetMessages(), false); } - - Connected?.Invoke(this, EventArgs.Empty); } catch (Exception) { - await DisconnectInternalAsync().ConfigureAwait(false); + await DisconnectAsync().ConfigureAwait(false); throw; } } - public async Task DisconnectAsync() + public new async Task PublishAsync(IEnumerable applicationMessages) { - if (!IsConnected) - { - return; - } - - try - { - await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false); - } - finally - { - await DisconnectInternalAsync().ConfigureAwait(false); - } + await InternalPublishAsync(applicationMessages, true); } - public async Task PublishAsync(IEnumerable applicationMessages) + private async Task InternalPublishAsync(IEnumerable applicationMessages, bool appendIfUsePersistance) { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed ThrowIfNotConnected(); foreach (var applicationMessage in applicationMessages) { - if (_usePersistance) - _persistentQueue.Enqueue(applicationMessage); + if (_usePersistance && appendIfUsePersistance) + await _persistentMessagesManager.SaveMessageAsync(applicationMessage); + _inflightQueue.Enqueue(applicationMessage); } -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed } - public async Task> SubscribeAsync(IEnumerable topicFilters) + public new async Task> SubscribeAsync(IEnumerable topicFilters) { - if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - - ThrowIfNotConnected(); - - var subscribePacket = new MqttSubscribePacket - { - PacketIdentifier = GetNewPacketIdentifier(), - TopicFilters = topicFilters.ToList() - }; - - var response = await SendAndReceiveAsync(subscribePacket).ConfigureAwait(false); - - if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count) - { - throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1]."); - } - - return subscribePacket.TopicFilters.Select((t, i) => new MqttSubscribeResult(t, response.SubscribeReturnCodes[i])).ToList(); + return await base.SubscribeAsync(topicFilters); } - public async Task UnsubscribeAsync(IEnumerable topicFilters) - { - if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - - ThrowIfNotConnected(); - - var unsubscribePacket = new MqttUnsubscribePacket - { - PacketIdentifier = GetNewPacketIdentifier(), - TopicFilters = topicFilters.ToList() - }; - - await SendAndReceiveAsync(unsubscribePacket); - } - - #region private private void ThrowIfNotConnected() { if (!IsConnected) throw new MqttCommunicationException("The client is not connected."); } - private void ThrowIfConnected(string message) - { - if (IsConnected) throw new MqttProtocolViolationException(message); - } - - private async Task SetupIncomingPacketProcessingAsync() - { - _isReceivingPackets = false; - -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Factory.StartNew( - () => ReceivePackets(_cancellationTokenSource.Token), - _cancellationTokenSource.Token, - TaskCreationOptions.LongRunning, - TaskScheduler.Default).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 && _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested) - { - await Task.Delay(TimeSpan.FromMilliseconds(100)); - } - } - - private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage) - { - var connectPacket = new MqttConnectPacket - { - ClientId = _options.ClientId, - Username = _options.UserName, - Password = _options.Password, - CleanSession = _options.CleanSession, - KeepAlivePeriod = (ushort)_options.KeepAlivePeriod.TotalSeconds, - WillMessage = willApplicationMessage - }; - - var response = await SendAndReceiveAsync(connectPacket).ConfigureAwait(false); - if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted) - { - throw new MqttConnectingFailedException(response.ConnectReturnCode); - } - } - - private async Task ReceivePackets(CancellationToken cancellationToken) - { - MqttNetTrace.Information(nameof(MqttClient), "Start receiving packets."); - - try - { - while (!cancellationToken.IsCancellationRequested) - { - _isReceivingPackets = true; - - var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); - if (cancellationToken.IsCancellationRequested) - { - return; - } - - StartProcessReceivedPacket(packet, cancellationToken); - } - } - catch (OperationCanceledException) - { - } - catch (MqttCommunicationException exception) - { - if (cancellationToken.IsCancellationRequested) - { - return; - } - - MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets."); - await DisconnectInternalAsync().ConfigureAwait(false); - } - catch (Exception exception) - { - MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets."); - await DisconnectInternalAsync().ConfigureAwait(false); - } - finally - { - MqttNetTrace.Information(nameof(MqttClient), "Stopped receiving packets."); - } - } - - private void StartProcessReceivedPacket(MqttBasePacket packet, CancellationToken cancellationToken) - { -#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), 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 ProcessReceivedPacketAsync(MqttBasePacket packet) - { - try - { - MqttNetTrace.Information(nameof(MqttClient), "Received <<< {0}", packet); - - if (packet is MqttPingReqPacket) - { - await SendAsync(new MqttPingRespPacket()); - return; - } - - if (packet is MqttDisconnectPacket) - { - await DisconnectAsync(); - return; - } - - if (packet is MqttPublishPacket publishPacket) - { - await ProcessReceivedPublishPacket(publishPacket); - return; - } - - if (packet is MqttPubRelPacket pubRelPacket) - { - await ProcessReceivedPubRelPacket(pubRelPacket); - return; - } - - _packetDispatcher.Dispatch(packet); - } - catch (Exception exception) - { - MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while processing received packet."); - } - } - - private async Task ProcessReceivedPublishPacket(MqttPublishPacket publishPacket) - { - if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) - { - FireApplicationMessageReceivedEvent(publishPacket); - return; - } - - if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) - { - FireApplicationMessageReceivedEvent(publishPacket); - await SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }); - return; - } - - if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) - { - // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] - lock (_unacknowledgedPublishPackets) - { - _unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier); - } - - FireApplicationMessageReceivedEvent(publishPacket); - await SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }); - return; - } - - throw new MqttCommunicationException("Received a not supported QoS level."); - } - - private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket) - { - lock (_unacknowledgedPublishPackets) - { - _unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier); - } - - return SendAsync(pubRelPacket.CreateResponse()); - } - - private async Task DisconnectInternalAsync() - { - var cts = _cancellationTokenSource; - if (cts == null || cts.IsCancellationRequested) - { - return; - } - - cts.Cancel(false); - cts.Dispose(); - _cancellationTokenSource = null; - - try - { - await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); - MqttNetTrace.Information(nameof(MqttClient), "Disconnected from adapter."); - } - catch (Exception exception) - { - MqttNetTrace.Warning(nameof(MqttClient), exception, "Error while disconnecting from adapter."); - } - finally - { - Disconnected?.Invoke(this, EventArgs.Empty); - } - } - - private void StartSendKeepAliveMessages(CancellationToken cancellationToken) - { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Factory.StartNew(() => SendKeepAliveMessagesAsync(cancellationToken), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).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 SendKeepAliveMessagesAsync(CancellationToken cancellationToken) - { - MqttNetTrace.Information(nameof(MqttClient), "Start sending keep alive packets."); - - try - { - while (!cancellationToken.IsCancellationRequested) - { - await Task.Delay(_options.KeepAlivePeriod, cancellationToken).ConfigureAwait(false); - if (cancellationToken.IsCancellationRequested) - { - return; - } - - await SendAndReceiveAsync(new MqttPingReqPacket()).ConfigureAwait(false); - } - } - catch (OperationCanceledException) - { - } - catch (MqttCommunicationException exception) - { - if (cancellationToken.IsCancellationRequested) - { - return; - } - - MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending/receiving keep alive packets."); - await DisconnectInternalAsync().ConfigureAwait(false); - } - catch (Exception exception) - { - MqttNetTrace.Warning(nameof(MqttClient), exception, "Unhandled exception while sending/receiving keep alive packets."); - await DisconnectInternalAsync().ConfigureAwait(false); - } - finally - { - MqttNetTrace.Information(nameof(MqttClient), "Stopped sending keep alive packets."); - } - } - - private async Task SendAndReceiveAsync(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket - { - var packetAwaiter = _packetDispatcher.WaitForPacketAsync(requestPacket, typeof(TResponsePacket), _options.DefaultCommunicationTimeout); - await _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false); - return (TResponsePacket)await packetAwaiter.ConfigureAwait(false); - } - - private Task SendAsync(MqttBasePacket packet) - { - return _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, packet); - } - private ushort GetNewPacketIdentifier() { return (ushort)Interlocked.Increment(ref _latestPacketIdentifier); } + - private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket) - { - try - { - var applicationMessage = publishPacket.ToApplicationMessage(); - ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(applicationMessage)); - } - catch (Exception exception) - { - MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while handling application message."); - } - } - #endregion - #endregion - - #region MqttClientQueued Methos private void SetupOutgoingPacketProcessingAsync() { #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed Task.Factory.StartNew( - () => SendPackets(_cancellationTokenSource.Token), - _cancellationTokenSource.Token, + () => SendPackets(base._cancellationTokenSource.Token), + base._cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - - //while (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested) - //{ - // await Task.Delay(TimeSpan.FromMilliseconds(100)); - //} } private async Task SendPackets(CancellationToken cancellationToken) @@ -480,21 +114,21 @@ namespace MQTTnet.Core.Client case MqttQualityOfServiceLevel.AtMostOnce: { // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] - await _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, publishPacket); + await base._adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, base._cancellationTokenSource.Token, publishPacket); break; } case MqttQualityOfServiceLevel.AtLeastOnce: { publishPacket.PacketIdentifier = GetNewPacketIdentifier(); - await SendAndReceiveAsync(publishPacket); + await base.SendAndReceiveAsync(publishPacket); break; } case MqttQualityOfServiceLevel.ExactlyOnce: { publishPacket.PacketIdentifier = GetNewPacketIdentifier(); - var pubRecPacket = await SendAndReceiveAsync(publishPacket).ConfigureAwait(false); - await SendAndReceiveAsync(pubRecPacket.CreateResponse()).ConfigureAwait(false); + var pubRecPacket = await base.SendAndReceiveAsync(publishPacket).ConfigureAwait(false); + await base.SendAndReceiveAsync(pubRecPacket.CreateResponse()).ConfigureAwait(false); break; } default: @@ -502,6 +136,9 @@ namespace MQTTnet.Core.Client throw new InvalidOperationException(); } } + //delete from persistence + if (_usePersistance) + await _persistentMessagesManager.Remove(messageToSend); } }; } @@ -518,13 +155,12 @@ namespace MQTTnet.Core.Client catch (Exception exception) { MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while sending packets."); - await DisconnectInternalAsync().ConfigureAwait(false); + await DisconnectAsync().ConfigureAwait(false); } finally { MqttNetTrace.Information(nameof(MqttClient), "Stopped sending packets."); } } - #endregion } } diff --git a/MQTTnet.Core/Client/MqttClientQueuedOptions.cs b/MQTTnet.Core/Client/MqttClientQueuedOptions.cs new file mode 100644 index 00000000..e7af0d78 --- /dev/null +++ b/MQTTnet.Core/Client/MqttClientQueuedOptions.cs @@ -0,0 +1,11 @@ + + +namespace MQTTnet.Core.Client +{ + public class MqttClientQueuedOptions: MqttClientTcpOptions + { + public bool UsePersistence { get; set; } + + public IMqttClientQueuedStorage Storage { get; set; } + } +} diff --git a/MQTTnet.Core/Client/MqttClientQueuedPersistentMessagesManager.cs b/MQTTnet.Core/Client/MqttClientQueuedPersistentMessagesManager.cs new file mode 100644 index 00000000..235b2bf6 --- /dev/null +++ b/MQTTnet.Core/Client/MqttClientQueuedPersistentMessagesManager.cs @@ -0,0 +1,84 @@ +using MQTTnet.Core.Diagnostics; +using MQTTnet.Core.Packets; +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MQTTnet.Core.Client +{ + public class MqttClientQueuedPersistentMessagesManager + { + private readonly IList _persistedMessages = new List(); + private readonly MqttClientQueuedOptions _options; + + public MqttClientQueuedPersistentMessagesManager(MqttClientQueuedOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + } + + public async Task LoadMessagesAsync() + { + try + { + var persistentMessages = await _options.Storage.LoadInflightMessagesAsync(); + lock (_persistedMessages) + { + _persistedMessages.Clear(); + foreach (var persistentMessage in persistentMessages) + { + _persistedMessages.Add(persistentMessage); + } + } + } + catch (Exception exception) + { + MqttNetTrace.Error(nameof(MqttClientQueuedPersistentMessagesManager), exception, "Unhandled exception while loading persistent messages."); + } + } + + public async Task SaveMessageAsync(MqttApplicationMessage applicationMessage) + { + if (applicationMessage != null) + { + lock (_persistedMessages) + { + _persistedMessages.Add(applicationMessage); + } + } + try + { + if (_options.Storage != null) + { + await _options.Storage.SaveInflightMessagesAsync(_persistedMessages); + } + } + catch (Exception exception) + { + MqttNetTrace.Error(nameof(MqttClientQueuedPersistentMessagesManager), exception, "Unhandled exception while saving persistent messages."); + } + } + + public List GetMessages() + { + var persistedMessages = new List(); + lock (_persistedMessages) + { + foreach (var persistedMessage in _persistedMessages) + { + persistedMessages.Add(persistedMessage); + } + } + + return persistedMessages; + } + + public async Task Remove(MqttApplicationMessage message) + { + lock (_persistedMessages) + _persistedMessages.Remove(message); + + await SaveMessageAsync(null); + } + } +} diff --git a/MQTTnet.Core/MQTTnet.Core.csproj b/MQTTnet.Core/MQTTnet.Core.csproj index 1e507cb7..50805933 100644 --- a/MQTTnet.Core/MQTTnet.Core.csproj +++ b/MQTTnet.Core/MQTTnet.Core.csproj @@ -22,20 +22,6 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs index c83f83ae..1b451ee4 100644 --- a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs +++ b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs @@ -12,7 +12,7 @@ namespace MQTTnet.Core.Tests _adapter = adapter; } - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options) + public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientQueuedOptions options) { return _adapter; } diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index f2a44e0c..be69c9a7 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -6,6 +6,7 @@ using MQTTnet.Core.Protocol; using MQTTnet.Core.Server; using System; using System.Collections.Generic; +using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -57,7 +58,7 @@ namespace MQTTnet.TestApp.NetCore try { - var options = new MqttClientTcpOptions + var options = new MqttClientQueuedOptions { Server = "192.168.0.14", ClientId = "XYZ", @@ -66,7 +67,8 @@ namespace MQTTnet.TestApp.NetCore Password = "passworda", KeepAlivePeriod = TimeSpan.FromSeconds(31), DefaultCommunicationTimeout = TimeSpan.FromSeconds(20), - + UsePersistence = true, + Storage = new TestStorage(), }; var client = new MqttClientFactory().CreateMqttQueuedClient(); @@ -272,5 +274,70 @@ namespace MQTTnet.TestApp.NetCore Console.ReadLine(); } + + [Serializable] + public sealed class TemporaryApplicationMessage + { + public TemporaryApplicationMessage(string topic, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain) + { + Topic = topic ?? throw new ArgumentNullException(nameof(topic)); + Payload = payload ?? throw new ArgumentNullException(nameof(payload)); + QualityOfServiceLevel = qualityOfServiceLevel; + Retain = retain; + } + + public string Topic { get; } + + public byte[] Payload { get; } + + public MqttQualityOfServiceLevel QualityOfServiceLevel { get; } + + public bool Retain { get; } + } + + private class TestStorage : IMqttClientQueuedStorage + { + string serializationFile = Path.Combine(Environment.CurrentDirectory, "messages.bin"); + private IList _messages = new List(); + + public Task> LoadInflightMessagesAsync() + { + //deserialize + // MqttApplicationMessage is not serializable + if (File.Exists(serializationFile)) + { + using (Stream stream = File.Open(serializationFile, FileMode.Open)) + { + var bformatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); + + var temp = (List)bformatter.Deserialize(stream); + foreach (var a in temp) + { + _messages.Add(new MqttApplicationMessage(a.Topic, a.Payload, a.QualityOfServiceLevel, a.Retain)); + } + } + } + return Task.FromResult(_messages); + } + + public Task SaveInflightMessagesAsync(IList messages) + { + _messages = messages; + //serialize + // MqttApplicationMessage is not serializable + using (System.IO.Stream stream = File.Open(serializationFile, FileMode.Create)) + { + var bformatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); + IList temp = new List(); + foreach (var m in _messages) + { + temp.Add(new TemporaryApplicationMessage(m.Topic, m.Payload, m.QualityOfServiceLevel, m.Retain)); + } + bformatter.Serialize(stream, temp); + } + + return Task.FromResult(0); + } + } } } diff --git a/Tests/MQTTnet.TestApp.NetCore/messages.bin b/Tests/MQTTnet.TestApp.NetCore/messages.bin new file mode 100644 index 0000000000000000000000000000000000000000..59505fa898386f36e09b72ce0a44d1f4a8c2c083 GIT binary patch literal 474 zcmbu6K}*9h7=|;~xeomiy<5U|9z948JLty5uuCajRz5~BP0IHrLj6_yhngrm7DV4b zLJoP}CkdM{#y&&h8gqXWrrq{M2m_7_Aotk!d$Sltn}J>gu;M# zD0QCg{Yk6#2?k-`z!b)7okaL$@;Z=%j+a(zXkDNX-+%#BEq_+zj;pE~>E(O0XO!K8 zfUdVFaR^eOMMlaNh~zoAjU4`?bX`w_^=+eqGtC-OACNZhKi5wfD@NLWqgvJRk<)Y$ S8?KpuZ?4~vtpCA}^t&%d=9cgP literal 0 HcmV?d00001 diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 07d5955f..8a22f5ea 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -41,7 +41,7 @@ namespace MQTTnet.TestApp.UniversalWindows private async void Connect(object sender, RoutedEventArgs e) { - MqttClientOptions options = null; + MqttClientQueuedOptions options = null; if (UseTcp.IsChecked == true) { options = new MqttClientTcpOptions From 1f3b24d20faba1fd91be4c1d25ebd512c7b4ad10 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Wed, 18 Oct 2017 17:14:13 +0200 Subject: [PATCH 10/34] Refactoring --- .../Implementations/MqttCommunicationAdapterFactory.cs | 8 +++++++- .../Implementations/MqttTcpChannel.cs | 2 +- .../MqttCommunicationAdapterFactory.cs | 2 +- Tests/MQTTnet.TestApp.NetCore/Program.cs | 9 ++++----- Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs index 2bb7c5ef..36a34b1f 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs @@ -7,7 +7,7 @@ namespace MQTTnet.Implementations { public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory { - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientQueuedOptions options) + public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -21,6 +21,12 @@ namespace MQTTnet.Implementations return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); } + if (options is MqttClientQueuedOptions queuedOptions) + { + return new MqttChannelCommunicationAdapter(new MqttTcpChannel(queuedOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); + } + + throw new NotSupportedException(); } } diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs index db102912..03206f1f 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs @@ -89,7 +89,7 @@ namespace MQTTnet.Implementations RawReceiveStream = ReceiveStream; } - private static Certificate LoadCertificate(MqttClientQueuedOptions options) + private static Certificate LoadCertificate(MqttClientOptions options) { if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any()) { diff --git a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs index 1b451ee4..c83f83ae 100644 --- a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs +++ b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs @@ -12,7 +12,7 @@ namespace MQTTnet.Core.Tests _adapter = adapter; } - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientQueuedOptions options) + public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options) { return _adapter; } diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index be69c9a7..63e43808 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -6,7 +6,6 @@ using MQTTnet.Core.Protocol; using MQTTnet.Core.Server; using System; using System.Collections.Generic; -using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -297,16 +296,16 @@ namespace MQTTnet.TestApp.NetCore private class TestStorage : IMqttClientQueuedStorage { - string serializationFile = Path.Combine(Environment.CurrentDirectory, "messages.bin"); + string serializationFile = System.IO.Path.Combine(Environment.CurrentDirectory, "messages.bin"); private IList _messages = new List(); public Task> LoadInflightMessagesAsync() { //deserialize // MqttApplicationMessage is not serializable - if (File.Exists(serializationFile)) + if (System.IO.File.Exists(serializationFile)) { - using (Stream stream = File.Open(serializationFile, FileMode.Open)) + using (System.IO.Stream stream = System.IO.File.Open(serializationFile, System.IO.FileMode.Open)) { var bformatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); @@ -325,7 +324,7 @@ namespace MQTTnet.TestApp.NetCore _messages = messages; //serialize // MqttApplicationMessage is not serializable - using (System.IO.Stream stream = File.Open(serializationFile, FileMode.Create)) + using (System.IO.Stream stream = System.IO.File.Open(serializationFile, System.IO.FileMode.Create)) { var bformatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); IList temp = new List(); diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 8a22f5ea..07d5955f 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -41,7 +41,7 @@ namespace MQTTnet.TestApp.UniversalWindows private async void Connect(object sender, RoutedEventArgs e) { - MqttClientQueuedOptions options = null; + MqttClientOptions options = null; if (UseTcp.IsChecked == true) { options = new MqttClientTcpOptions From 870ed00e3c32c45797815235a34d8bfd2812bb16 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Thu, 19 Oct 2017 13:21:06 +0200 Subject: [PATCH 11/34] Refactoring Added almost of the hits. Options modified but must be reviewed completely. --- .../MqttCommunicationAdapterFactory.cs | 4 +- .../Implementations/MqttTcpChannel.cs | 2 +- .../MQTTnet.NetStandard/MqttClientFactory.cs | 4 +- .../MqttCommunicationAdapterFactory.cs | 4 +- .../Implementations/MqttTcpChannel.cs | 2 +- .../MqttClientFactory.cs | 4 +- MQTTnet.Core/Client/IMqttClient.cs | 2 +- MQTTnet.Core/Client/IMqttClientFactory.cs | 2 +- MQTTnet.Core/Client/IMqttClientManaged.cs | 9 + MQTTnet.Core/Client/IMqttClientQueued.cs | 9 - .../Client/IMqttClientQueuedStorage.cs | 4 +- .../IMqttCommunicationAdapterFactory.cs | 2 +- MQTTnet.Core/Client/MqttClient.cs | 5 +- MQTTnet.Core/Client/MqttClientManaged .cs | 163 +++++++++++++++++ .../Client/MqttClientManagedOptions.cs | 12 ++ MQTTnet.Core/Client/MqttClientOptions.cs | 24 +-- MQTTnet.Core/Client/MqttClientQueued.cs | 166 ------------------ .../Client/MqttClientQueuedOptions.cs | 11 -- ...ttClientQueuedPersistentMessagesManager.cs | 8 +- MQTTnet.Core/Client/MqttClientTcpOptions.cs | 26 ++- .../Client/MqttClientWebSocketOptions.cs | 26 ++- .../MqttCommunicationAdapterFactory.cs | 2 +- Tests/MQTTnet.TestApp.NetCore/Program.cs | 11 +- .../MainPage.xaml.cs | 2 +- 24 files changed, 273 insertions(+), 231 deletions(-) create mode 100644 MQTTnet.Core/Client/IMqttClientManaged.cs delete mode 100644 MQTTnet.Core/Client/IMqttClientQueued.cs create mode 100644 MQTTnet.Core/Client/MqttClientManaged .cs create mode 100644 MQTTnet.Core/Client/MqttClientManagedOptions.cs delete mode 100644 MQTTnet.Core/Client/MqttClientQueued.cs delete mode 100644 MQTTnet.Core/Client/MqttClientQueuedOptions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs index 218bb8cf..ceaac98b 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs @@ -7,7 +7,7 @@ namespace MQTTnet.Implementations { public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory { - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options) + public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -21,7 +21,7 @@ namespace MQTTnet.Implementations return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); } - if (options is MqttClientQueuedOptions queuedOptions) + if (options is MqttClientManagedOptions queuedOptions) { return new MqttChannelCommunicationAdapter(new MqttTcpChannel(queuedOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); } diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs index dfe36540..2fe18043 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs @@ -121,7 +121,7 @@ namespace MQTTnet.Implementations return _options.TlsOptions.AllowUntrustedCertificates; } - private static X509CertificateCollection LoadCertificates(MqttClientOptions options) + private static X509CertificateCollection LoadCertificates(IMqttClientOptions options) { var certificates = new X509CertificateCollection(); if (options.TlsOptions.Certificates == null) diff --git a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs index 5bc8336f..53ce2765 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs @@ -10,9 +10,9 @@ namespace MQTTnet return new MqttClient(new MqttCommunicationAdapterFactory()); } - public IMqttClientQueued CreateMqttQueuedClient() + public IMqttClientManaged CreateMqttManagedClient() { - return new MqttClientQueued(new MqttCommunicationAdapterFactory()); + return new MqttClientManaged(new MqttCommunicationAdapterFactory()); } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs index 36a34b1f..9e429fd1 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs @@ -7,7 +7,7 @@ namespace MQTTnet.Implementations { public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory { - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options) + public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -21,7 +21,7 @@ namespace MQTTnet.Implementations return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); } - if (options is MqttClientQueuedOptions queuedOptions) + if (options is MqttClientManagedOptions queuedOptions) { return new MqttChannelCommunicationAdapter(new MqttTcpChannel(queuedOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); } diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs index 03206f1f..5335daed 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs @@ -89,7 +89,7 @@ namespace MQTTnet.Implementations RawReceiveStream = ReceiveStream; } - private static Certificate LoadCertificate(MqttClientOptions options) + private static Certificate LoadCertificate(IMqttClientOptions options) { if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any()) { diff --git a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs b/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs index 5bc8336f..53ce2765 100644 --- a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs +++ b/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs @@ -10,9 +10,9 @@ namespace MQTTnet return new MqttClient(new MqttCommunicationAdapterFactory()); } - public IMqttClientQueued CreateMqttQueuedClient() + public IMqttClientManaged CreateMqttManagedClient() { - return new MqttClientQueued(new MqttCommunicationAdapterFactory()); + return new MqttClientManaged(new MqttCommunicationAdapterFactory()); } } } \ No newline at end of file diff --git a/MQTTnet.Core/Client/IMqttClient.cs b/MQTTnet.Core/Client/IMqttClient.cs index 7622867b..b46a2bcd 100644 --- a/MQTTnet.Core/Client/IMqttClient.cs +++ b/MQTTnet.Core/Client/IMqttClient.cs @@ -13,7 +13,7 @@ namespace MQTTnet.Core.Client event EventHandler Connected; event EventHandler Disconnected; - Task ConnectAsync(MqttClientOptions options); + Task ConnectAsync(IMqttClientOptions options); Task DisconnectAsync(); Task> SubscribeAsync(IEnumerable topicFilters); diff --git a/MQTTnet.Core/Client/IMqttClientFactory.cs b/MQTTnet.Core/Client/IMqttClientFactory.cs index f1d64114..25e03b39 100644 --- a/MQTTnet.Core/Client/IMqttClientFactory.cs +++ b/MQTTnet.Core/Client/IMqttClientFactory.cs @@ -4,6 +4,6 @@ { IMqttClient CreateMqttClient(); - IMqttClientQueued CreateMqttQueuedClient(); + IMqttClientManaged CreateMqttManagedClient(); } } \ No newline at end of file diff --git a/MQTTnet.Core/Client/IMqttClientManaged.cs b/MQTTnet.Core/Client/IMqttClientManaged.cs new file mode 100644 index 00000000..1fc42f09 --- /dev/null +++ b/MQTTnet.Core/Client/IMqttClientManaged.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Core.Client +{ + public interface IMqttClientManaged : IMqttClient + { + //Task ConnectAsync(MqttClientManagedOptions options); + } +} diff --git a/MQTTnet.Core/Client/IMqttClientQueued.cs b/MQTTnet.Core/Client/IMqttClientQueued.cs deleted file mode 100644 index c172e0cc..00000000 --- a/MQTTnet.Core/Client/IMqttClientQueued.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace MQTTnet.Core.Client -{ - public interface IMqttClientQueued: IMqttClient - { - Task ConnectAsync(MqttClientQueuedOptions options); - } -} diff --git a/MQTTnet.Core/Client/IMqttClientQueuedStorage.cs b/MQTTnet.Core/Client/IMqttClientQueuedStorage.cs index ffc5e5f6..7909a563 100644 --- a/MQTTnet.Core/Client/IMqttClientQueuedStorage.cs +++ b/MQTTnet.Core/Client/IMqttClientQueuedStorage.cs @@ -5,8 +5,8 @@ namespace MQTTnet.Core.Client { public interface IMqttClientQueuedStorage { - Task SaveInflightMessagesAsync(IList messages); + Task SaveQueuedMessagesAsync(IList messages); - Task> LoadInflightMessagesAsync(); + Task> LoadQueuedMessagesAsync(); } } diff --git a/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs b/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs index 092ea040..0e295900 100644 --- a/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs +++ b/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs @@ -4,6 +4,6 @@ namespace MQTTnet.Core.Client { public interface IMqttCommunicationAdapterFactory { - IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options); + IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options); } } \ No newline at end of file diff --git a/MQTTnet.Core/Client/MqttClient.cs b/MQTTnet.Core/Client/MqttClient.cs index 002ebe83..6cdf0512 100644 --- a/MQTTnet.Core/Client/MqttClient.cs +++ b/MQTTnet.Core/Client/MqttClient.cs @@ -18,7 +18,7 @@ namespace MQTTnet.Core.Client private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); private readonly IMqttCommunicationAdapterFactory _communicationChannelFactory; - private MqttClientOptions _options; + private IMqttClientOptions _options; private bool _isReceivingPackets; private int _latestPacketIdentifier; internal CancellationTokenSource _cancellationTokenSource; @@ -35,7 +35,8 @@ namespace MQTTnet.Core.Client public bool IsConnected => _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested; - public async Task ConnectAsync(MqttClientOptions options) + + public async Task ConnectAsync(IMqttClientOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); diff --git a/MQTTnet.Core/Client/MqttClientManaged .cs b/MQTTnet.Core/Client/MqttClientManaged .cs new file mode 100644 index 00000000..03afc926 --- /dev/null +++ b/MQTTnet.Core/Client/MqttClientManaged .cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Core.Adapter; +using MQTTnet.Core.Diagnostics; +using MQTTnet.Core.Exceptions; +using MQTTnet.Core.Packets; +using MQTTnet.Core.Protocol; +using MQTTnet.Core.Internal; + +namespace MQTTnet.Core.Client +{ + public class MqttClientManaged: IMqttClientManaged + { + private MqttClientManagedOptions _options; + private int _latestPacketIdentifier; + private readonly BlockingCollection _inflightQueue; + private bool _usePersistance = false; + private MqttClientQueuedPersistentMessagesManager _persistentMessagesManager; + private readonly MqttClient _baseMqttClient; + + public MqttClientManaged(IMqttCommunicationAdapterFactory communicationChannelFactory) + { + _baseMqttClient = new MqttClient(communicationChannelFactory); + _baseMqttClient.Connected += BaseMqttClient_Connected; + _baseMqttClient.Disconnected += BaseMqttClient_Disconnected; + _baseMqttClient.ApplicationMessageReceived += BaseMqttClient_ApplicationMessageReceived; + _inflightQueue = new BlockingCollection(); + } + + private void BaseMqttClient_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e) + { + ApplicationMessageReceived?.Invoke(this, e); + } + + private void BaseMqttClient_Disconnected(object sender, EventArgs e) + { + Disconnected?.Invoke(this, e); + } + + private void BaseMqttClient_Connected(object sender, EventArgs e) + { + Connected?.Invoke(this, e); + } + + public event EventHandler Connected; + public event EventHandler Disconnected; + public event EventHandler ApplicationMessageReceived; + + public bool IsConnected => _baseMqttClient.IsConnected; + + + public async Task ConnectAsync(IMqttClientOptions options) + { + //TODO VERY BAD + _options = options as MqttClientManagedOptions; + this._usePersistance = _options.Storage != null; + await _baseMqttClient.ConnectAsync(options); + SetupOutgoingPacketProcessingAsync(); + + //load persistentMessages + if (_usePersistance) + { + if (_persistentMessagesManager == null) + _persistentMessagesManager = new MqttClientQueuedPersistentMessagesManager(_options); + await _persistentMessagesManager.LoadMessagesAsync(); + await InternalPublishAsync(_persistentMessagesManager.GetMessages(), false); + } + } + + public async Task DisconnectAsync() + { + await _baseMqttClient.DisconnectAsync(); + } + + public async Task UnsubscribeAsync(IEnumerable topicFilters) + { + await _baseMqttClient.UnsubscribeAsync(topicFilters); + } + + public async Task PublishAsync(IEnumerable applicationMessages) + { + await InternalPublishAsync(applicationMessages, true); + } + + private async Task InternalPublishAsync(IEnumerable applicationMessages, bool appendIfUsePersistance) + { + ThrowIfNotConnected(); + + foreach (var applicationMessage in applicationMessages) + { + if (_usePersistance && appendIfUsePersistance) + await _persistentMessagesManager.SaveMessageAsync(applicationMessage); + + _inflightQueue.Add(applicationMessage); + } + } + + public async Task> SubscribeAsync(IEnumerable topicFilters) + { + return await _baseMqttClient.SubscribeAsync(topicFilters); + } + + private void ThrowIfNotConnected() + { + if (!IsConnected) throw new MqttCommunicationException("The client is not connected."); + } + + private ushort GetNewPacketIdentifier() + { + return (ushort)Interlocked.Increment(ref _latestPacketIdentifier); + } + + + private void SetupOutgoingPacketProcessingAsync() + { + Task.Factory.StartNew( + () => SendPackets(_baseMqttClient._cancellationTokenSource.Token), + _baseMqttClient._cancellationTokenSource.Token, + TaskCreationOptions.LongRunning, + TaskScheduler.Default).ConfigureAwait(false); + } + + private async Task SendPackets(CancellationToken cancellationToken) + { + MqttNetTrace.Information(nameof(MqttClientManaged), "Start sending packets."); + MqttApplicationMessage messageInQueue = null; + + try + { + while (!cancellationToken.IsCancellationRequested) + { + messageInQueue = _inflightQueue.Take(); + await _baseMqttClient.PublishAsync(new List() { messageInQueue }); + if (_usePersistance) + await _persistentMessagesManager.Remove(messageInQueue); + } + } + catch (OperationCanceledException) + { + } + catch (MqttCommunicationException exception) + { + MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending packets."); + //message not send, equeue it again + if (messageInQueue != null) + _inflightQueue.Add(messageInQueue); + } + catch (Exception exception) + { + MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while sending packets."); + await DisconnectAsync().ConfigureAwait(false); + } + finally + { + MqttNetTrace.Information(nameof(MqttClient), "Stopped sending packets."); + } + } + } +} diff --git a/MQTTnet.Core/Client/MqttClientManagedOptions.cs b/MQTTnet.Core/Client/MqttClientManagedOptions.cs new file mode 100644 index 00000000..77feabed --- /dev/null +++ b/MQTTnet.Core/Client/MqttClientManagedOptions.cs @@ -0,0 +1,12 @@ + +using System; + +namespace MQTTnet.Core.Client +{ + public class MqttClientManagedOptions: MqttClientTcpOptions + { + public bool UseAutoReconnect { get; set; } + public TimeSpan AutoReconnectDelay { get; set; } + public IMqttClientQueuedStorage Storage { get; set; } + } +} diff --git a/MQTTnet.Core/Client/MqttClientOptions.cs b/MQTTnet.Core/Client/MqttClientOptions.cs index f2e28fbb..35cdf5fb 100644 --- a/MQTTnet.Core/Client/MqttClientOptions.cs +++ b/MQTTnet.Core/Client/MqttClientOptions.cs @@ -1,26 +1,26 @@ -using System; -using MQTTnet.Core.Serializer; +using MQTTnet.Core.Serializer; +using System; namespace MQTTnet.Core.Client { - public abstract class MqttClientOptions + public interface IMqttClientOptions { - public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); + MqttClientTlsOptions TlsOptions { get; set; } - public MqttApplicationMessage WillMessage { get; set; } + MqttApplicationMessage WillMessage { get; set; } - public string UserName { get; set; } + string UserName { get; set; } - public string Password { get; set; } + string Password { get; set; } - public string ClientId { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty); + string ClientId { get; set; } - public bool CleanSession { get; set; } = true; + bool CleanSession { get; set; } - public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5); + TimeSpan KeepAlivePeriod { get; set; } - public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10); + TimeSpan DefaultCommunicationTimeout { get; set; } - public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; + MqttProtocolVersion ProtocolVersion { get; set; } } } diff --git a/MQTTnet.Core/Client/MqttClientQueued.cs b/MQTTnet.Core/Client/MqttClientQueued.cs deleted file mode 100644 index 05d5766a..00000000 --- a/MQTTnet.Core/Client/MqttClientQueued.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Exceptions; -using MQTTnet.Core.Packets; -using MQTTnet.Core.Protocol; -using MQTTnet.Core.Internal; - -namespace MQTTnet.Core.Client -{ - public class MqttClientQueued : MqttClient, IMqttClientQueued - { - private MqttClientQueuedOptions _options; - private int _latestPacketIdentifier; - private readonly ConcurrentQueue _inflightQueue; - private bool _usePersistance = false; - private MqttClientQueuedPersistentMessagesManager _persistentMessagesManager; - - public MqttClientQueued(IMqttCommunicationAdapterFactory communicationChannelFactory) : base(communicationChannelFactory) - { - _inflightQueue = new ConcurrentQueue(); - } - - - public async Task ConnectAsync(MqttClientQueuedOptions options) - { - try - { - _options = options; - this._usePersistance = _options.UsePersistence; - await base.ConnectAsync(options); - SetupOutgoingPacketProcessingAsync(); - - //load persistentMessages - if (_usePersistance) - { - if (_persistentMessagesManager == null) - _persistentMessagesManager = new MqttClientQueuedPersistentMessagesManager(_options); - await _persistentMessagesManager.LoadMessagesAsync(); - await InternalPublishAsync(_persistentMessagesManager.GetMessages(), false); - } - } - catch (Exception) - { - await DisconnectAsync().ConfigureAwait(false); - throw; - } - } - - public new async Task PublishAsync(IEnumerable applicationMessages) - { - await InternalPublishAsync(applicationMessages, true); - } - - private async Task InternalPublishAsync(IEnumerable applicationMessages, bool appendIfUsePersistance) - { - ThrowIfNotConnected(); - - foreach (var applicationMessage in applicationMessages) - { - if (_usePersistance && appendIfUsePersistance) - await _persistentMessagesManager.SaveMessageAsync(applicationMessage); - - _inflightQueue.Enqueue(applicationMessage); - } - } - - public new async Task> SubscribeAsync(IEnumerable topicFilters) - { - return await base.SubscribeAsync(topicFilters); - } - - private void ThrowIfNotConnected() - { - if (!IsConnected) throw new MqttCommunicationException("The client is not connected."); - } - - private ushort GetNewPacketIdentifier() - { - return (ushort)Interlocked.Increment(ref _latestPacketIdentifier); - } - - - private void SetupOutgoingPacketProcessingAsync() - { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Factory.StartNew( - () => SendPackets(base._cancellationTokenSource.Token), - base._cancellationTokenSource.Token, - TaskCreationOptions.LongRunning, - TaskScheduler.Default).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 SendPackets(CancellationToken cancellationToken) - { - MqttNetTrace.Information(nameof(MqttClient), "Start sending packets."); - MqttApplicationMessage messageToSend = null; - try - { - while (!cancellationToken.IsCancellationRequested) - { - while (_inflightQueue.TryDequeue(out messageToSend)) - { - MqttQualityOfServiceLevel qosGroup = messageToSend.QualityOfServiceLevel; - MqttPublishPacket publishPacket = messageToSend.ToPublishPacket(); - switch (qosGroup) - { - case MqttQualityOfServiceLevel.AtMostOnce: - { - // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] - await base._adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, base._cancellationTokenSource.Token, publishPacket); - break; - } - - case MqttQualityOfServiceLevel.AtLeastOnce: - { - publishPacket.PacketIdentifier = GetNewPacketIdentifier(); - await base.SendAndReceiveAsync(publishPacket); - break; - } - case MqttQualityOfServiceLevel.ExactlyOnce: - { - publishPacket.PacketIdentifier = GetNewPacketIdentifier(); - var pubRecPacket = await base.SendAndReceiveAsync(publishPacket).ConfigureAwait(false); - await base.SendAndReceiveAsync(pubRecPacket.CreateResponse()).ConfigureAwait(false); - break; - } - default: - { - throw new InvalidOperationException(); - } - } - //delete from persistence - if (_usePersistance) - await _persistentMessagesManager.Remove(messageToSend); - } - }; - } - catch (OperationCanceledException) - { - } - catch (MqttCommunicationException exception) - { - MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending packets."); - //message not send, equeued again - if (messageToSend != null) - _inflightQueue.Enqueue(messageToSend); - } - catch (Exception exception) - { - MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while sending packets."); - await DisconnectAsync().ConfigureAwait(false); - } - finally - { - MqttNetTrace.Information(nameof(MqttClient), "Stopped sending packets."); - } - } - } -} diff --git a/MQTTnet.Core/Client/MqttClientQueuedOptions.cs b/MQTTnet.Core/Client/MqttClientQueuedOptions.cs deleted file mode 100644 index e7af0d78..00000000 --- a/MQTTnet.Core/Client/MqttClientQueuedOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ - - -namespace MQTTnet.Core.Client -{ - public class MqttClientQueuedOptions: MqttClientTcpOptions - { - public bool UsePersistence { get; set; } - - public IMqttClientQueuedStorage Storage { get; set; } - } -} diff --git a/MQTTnet.Core/Client/MqttClientQueuedPersistentMessagesManager.cs b/MQTTnet.Core/Client/MqttClientQueuedPersistentMessagesManager.cs index 235b2bf6..b012e215 100644 --- a/MQTTnet.Core/Client/MqttClientQueuedPersistentMessagesManager.cs +++ b/MQTTnet.Core/Client/MqttClientQueuedPersistentMessagesManager.cs @@ -10,9 +10,9 @@ namespace MQTTnet.Core.Client public class MqttClientQueuedPersistentMessagesManager { private readonly IList _persistedMessages = new List(); - private readonly MqttClientQueuedOptions _options; + private readonly MqttClientManagedOptions _options; - public MqttClientQueuedPersistentMessagesManager(MqttClientQueuedOptions options) + public MqttClientQueuedPersistentMessagesManager(MqttClientManagedOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); } @@ -21,7 +21,7 @@ namespace MQTTnet.Core.Client { try { - var persistentMessages = await _options.Storage.LoadInflightMessagesAsync(); + var persistentMessages = await _options.Storage.LoadQueuedMessagesAsync(); lock (_persistedMessages) { _persistedMessages.Clear(); @@ -50,7 +50,7 @@ namespace MQTTnet.Core.Client { if (_options.Storage != null) { - await _options.Storage.SaveInflightMessagesAsync(_persistedMessages); + await _options.Storage.SaveQueuedMessagesAsync(_persistedMessages); } } catch (Exception exception) diff --git a/MQTTnet.Core/Client/MqttClientTcpOptions.cs b/MQTTnet.Core/Client/MqttClientTcpOptions.cs index beaa506a..98d27dc5 100644 --- a/MQTTnet.Core/Client/MqttClientTcpOptions.cs +++ b/MQTTnet.Core/Client/MqttClientTcpOptions.cs @@ -1,7 +1,29 @@ -namespace MQTTnet.Core.Client +using MQTTnet.Core.Serializer; +using System; + +namespace MQTTnet.Core.Client { - public class MqttClientTcpOptions : MqttClientOptions + public class MqttClientTcpOptions : IMqttClientOptions { + public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); + + public MqttApplicationMessage WillMessage { get; set; } + + public string UserName { get; set; } + + public string Password { get; set; } + + public string ClientId { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty); + + public bool CleanSession { get; set; } = true; + + public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5); + + public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10); + + public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; + + public string Server { get; set; } public int? Port { get; set; } diff --git a/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs b/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs index 4b905242..b7a904dd 100644 --- a/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs +++ b/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs @@ -1,7 +1,29 @@ -namespace MQTTnet.Core.Client +using System; +using MQTTnet.Core.Serializer; + +namespace MQTTnet.Core.Client { - public class MqttClientWebSocketOptions : MqttClientOptions + public class MqttClientWebSocketOptions : IMqttClientOptions { public string Uri { get; set; } + + public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); + + public MqttApplicationMessage WillMessage { get; set; } + + public string UserName { get; set; } + + public string Password { get; set; } + + public string ClientId { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty); + + public bool CleanSession { get; set; } = true; + + public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5); + + public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10); + + public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; + } } diff --git a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs index c83f83ae..bed32182 100644 --- a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs +++ b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs @@ -12,7 +12,7 @@ namespace MQTTnet.Core.Tests _adapter = adapter; } - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options) + public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options) { return _adapter; } diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 63e43808..eebe4b26 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -57,7 +57,7 @@ namespace MQTTnet.TestApp.NetCore try { - var options = new MqttClientQueuedOptions + var options = new MqttClientManagedOptions { Server = "192.168.0.14", ClientId = "XYZ", @@ -66,11 +66,10 @@ namespace MQTTnet.TestApp.NetCore Password = "passworda", KeepAlivePeriod = TimeSpan.FromSeconds(31), DefaultCommunicationTimeout = TimeSpan.FromSeconds(20), - UsePersistence = true, Storage = new TestStorage(), }; - var client = new MqttClientFactory().CreateMqttQueuedClient(); + var client = new MqttClientFactory().CreateMqttManagedClient(); client.ApplicationMessageReceived += (s, e) => { Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); @@ -177,7 +176,7 @@ namespace MQTTnet.TestApp.NetCore await client.SubscribeAsync(new List { - new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce) + new TopicFilter("#", MqttQualityOfServiceLevel.ExactlyOnce) }); Console.WriteLine("### SUBSCRIBED ###"); @@ -299,7 +298,7 @@ namespace MQTTnet.TestApp.NetCore string serializationFile = System.IO.Path.Combine(Environment.CurrentDirectory, "messages.bin"); private IList _messages = new List(); - public Task> LoadInflightMessagesAsync() + public Task> LoadQueuedMessagesAsync() { //deserialize // MqttApplicationMessage is not serializable @@ -319,7 +318,7 @@ namespace MQTTnet.TestApp.NetCore return Task.FromResult(_messages); } - public Task SaveInflightMessagesAsync(IList messages) + public Task SaveQueuedMessagesAsync(IList messages) { _messages = messages; //serialize diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 07d5955f..4565626c 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -41,7 +41,7 @@ namespace MQTTnet.TestApp.UniversalWindows private async void Connect(object sender, RoutedEventArgs e) { - MqttClientOptions options = null; + IMqttClientOptions options = null; if (UseTcp.IsChecked == true) { options = new MqttClientTcpOptions From 40b1be075c329303e7029c4b7e5b0e5b99307f6f Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 19 Oct 2017 18:50:22 +0200 Subject: [PATCH 12/34] Refactoring --- .../MqttCommunicationAdapterFactory.cs | 2 +- .../Implementations/MqttTcpChannel.cs | 5 +- .../MqttCommunicationAdapterFactory.cs | 2 +- .../Implementations/MqttTcpChannel.cs | 2 +- ...entOptions.cs => BaseMqttClientOptions.cs} | 4 +- MQTTnet.Core/Client/IMqttClient.cs | 4 +- MQTTnet.Core/Client/IMqttClientOptions.cs | 18 +++++++ .../IMqttCommunicationAdapterFactory.cs | 2 +- MQTTnet.Core/Client/MqttClient.cs | 14 +++-- .../Client/MqttClientDisconnectedEventArgs.cs | 14 +++++ MQTTnet.Core/Client/MqttClientTcpOptions.cs | 2 +- .../Client/MqttClientWebSocketOptions.cs | 2 +- .../Exceptions/MqttCommunicationException.cs | 5 -- MQTTnet.Core/MQTTnet.Core.csproj | 4 ++ MQTTnet.Core/MqttApplicationMessageFactory.cs | 7 --- MQTTnet.Core/Packets/MqttPacketExtensions.cs | 3 +- MQTTnet.Core/Packets/MqttSubAckPacket.cs | 2 +- .../Serializer/MqttPacketSerializer.cs | 31 ++++------- MQTTnet.Core/Server/IMqttServer.cs | 3 +- .../Server/MqttClientSessionsManager.cs | 2 +- MQTTnet.Core/Server/MqttServer.cs | 2 +- Tests/MQTTnet.Core.Tests/ExtensionTests.cs | 2 +- .../MqttApplicationMessageFactoryTests.cs | 30 +++++++++++ .../MqttCommunicationAdapterFactory.cs | 2 +- .../MqttPacketSerializerTests.cs | 41 --------------- .../MQTTnet.TestApp.NetCore.csproj | 4 ++ .../PerformanceTest.cs | 8 ++- Tests/MQTTnet.TestApp.NetCore/Program.cs | 51 +++++++++++++++++-- .../App.xaml.cs | 27 ++-------- .../MainPage.xaml | 4 +- .../MainPage.xaml.cs | 4 +- 31 files changed, 166 insertions(+), 137 deletions(-) rename MQTTnet.Core/Client/{MqttClientOptions.cs => BaseMqttClientOptions.cs} (89%) create mode 100644 MQTTnet.Core/Client/IMqttClientOptions.cs create mode 100644 MQTTnet.Core/Client/MqttClientDisconnectedEventArgs.cs create mode 100644 Tests/MQTTnet.Core.Tests/MqttApplicationMessageFactoryTests.cs diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs index 799ccddd..05e6fb63 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs @@ -7,7 +7,7 @@ namespace MQTTnet.Implementations { public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory { - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options) + public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs index dfe36540..cd9151e6 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs @@ -48,8 +48,7 @@ namespace MQTTnet.Implementations public Stream SendStream { get; private set; } public Stream ReceiveStream { get; private set; } public Stream RawReceiveStream { get; private set; } - - + public static Func CustomCertificateValidationCallback { get; set; } public async Task ConnectAsync() @@ -121,7 +120,7 @@ namespace MQTTnet.Implementations return _options.TlsOptions.AllowUntrustedCertificates; } - private static X509CertificateCollection LoadCertificates(MqttClientOptions options) + private static X509CertificateCollection LoadCertificates(IMqttClientOptions options) { var certificates = new X509CertificateCollection(); if (options.TlsOptions.Certificates == null) diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs index 799ccddd..05e6fb63 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs @@ -7,7 +7,7 @@ namespace MQTTnet.Implementations { public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory { - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options) + public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs index 03206f1f..1a96f1e4 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs @@ -89,7 +89,7 @@ namespace MQTTnet.Implementations RawReceiveStream = ReceiveStream; } - private static Certificate LoadCertificate(MqttClientOptions options) + private static Certificate LoadCertificate(BaseMqttClientOptions options) { if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any()) { diff --git a/MQTTnet.Core/Client/MqttClientOptions.cs b/MQTTnet.Core/Client/BaseMqttClientOptions.cs similarity index 89% rename from MQTTnet.Core/Client/MqttClientOptions.cs rename to MQTTnet.Core/Client/BaseMqttClientOptions.cs index f2e28fbb..ca3f82e2 100644 --- a/MQTTnet.Core/Client/MqttClientOptions.cs +++ b/MQTTnet.Core/Client/BaseMqttClientOptions.cs @@ -3,7 +3,7 @@ using MQTTnet.Core.Serializer; namespace MQTTnet.Core.Client { - public abstract class MqttClientOptions + public abstract class BaseMqttClientOptions : IMqttClientOptions { public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); @@ -13,7 +13,7 @@ namespace MQTTnet.Core.Client public string Password { get; set; } - public string ClientId { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty); + public string ClientId { get; set; } = Guid.NewGuid().ToString("N"); public bool CleanSession { get; set; } = true; diff --git a/MQTTnet.Core/Client/IMqttClient.cs b/MQTTnet.Core/Client/IMqttClient.cs index 7622867b..7219c58d 100644 --- a/MQTTnet.Core/Client/IMqttClient.cs +++ b/MQTTnet.Core/Client/IMqttClient.cs @@ -11,9 +11,9 @@ namespace MQTTnet.Core.Client event EventHandler ApplicationMessageReceived; event EventHandler Connected; - event EventHandler Disconnected; + event EventHandler Disconnected; - Task ConnectAsync(MqttClientOptions options); + Task ConnectAsync(IMqttClientOptions options); Task DisconnectAsync(); Task> SubscribeAsync(IEnumerable topicFilters); diff --git a/MQTTnet.Core/Client/IMqttClientOptions.cs b/MQTTnet.Core/Client/IMqttClientOptions.cs new file mode 100644 index 00000000..7e675ff8 --- /dev/null +++ b/MQTTnet.Core/Client/IMqttClientOptions.cs @@ -0,0 +1,18 @@ +using System; +using MQTTnet.Core.Serializer; + +namespace MQTTnet.Core.Client +{ + public interface IMqttClientOptions + { + bool CleanSession { get; } + string ClientId { get; } + TimeSpan DefaultCommunicationTimeout { get; } + TimeSpan KeepAlivePeriod { get; } + string Password { get; } + MqttProtocolVersion ProtocolVersion { get; } + MqttClientTlsOptions TlsOptions { get; } + string UserName { get; } + MqttApplicationMessage WillMessage { get; } + } +} \ No newline at end of file diff --git a/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs b/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs index 092ea040..0e295900 100644 --- a/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs +++ b/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs @@ -4,6 +4,6 @@ namespace MQTTnet.Core.Client { public interface IMqttCommunicationAdapterFactory { - IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options); + IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options); } } \ No newline at end of file diff --git a/MQTTnet.Core/Client/MqttClient.cs b/MQTTnet.Core/Client/MqttClient.cs index 80b850a8..53857900 100644 --- a/MQTTnet.Core/Client/MqttClient.cs +++ b/MQTTnet.Core/Client/MqttClient.cs @@ -18,7 +18,7 @@ namespace MQTTnet.Core.Client private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); private readonly IMqttCommunicationAdapterFactory _communicationChannelFactory; - private MqttClientOptions _options; + private IMqttClientOptions _options; private bool _isReceivingPackets; private int _latestPacketIdentifier; private CancellationTokenSource _cancellationTokenSource; @@ -30,12 +30,12 @@ namespace MQTTnet.Core.Client } public event EventHandler Connected; - public event EventHandler Disconnected; + public event EventHandler Disconnected; public event EventHandler ApplicationMessageReceived; - public bool IsConnected => _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested; + public bool IsConnected { get; private set; } - public async Task ConnectAsync(MqttClientOptions options) + public async Task ConnectAsync(IMqttClientOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -64,6 +64,7 @@ namespace MQTTnet.Core.Client StartSendKeepAliveMessages(_cancellationTokenSource.Token); } + IsConnected = true; Connected?.Invoke(this, EventArgs.Empty); } catch (Exception) @@ -222,6 +223,9 @@ namespace MQTTnet.Core.Client private async Task DisconnectInternalAsync() { + var clientWasConnected = IsConnected; + IsConnected = false; + var cts = _cancellationTokenSource; if (cts == null || cts.IsCancellationRequested) { @@ -243,7 +247,7 @@ namespace MQTTnet.Core.Client } finally { - Disconnected?.Invoke(this, EventArgs.Empty); + Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected)); } } diff --git a/MQTTnet.Core/Client/MqttClientDisconnectedEventArgs.cs b/MQTTnet.Core/Client/MqttClientDisconnectedEventArgs.cs new file mode 100644 index 00000000..1e9609ef --- /dev/null +++ b/MQTTnet.Core/Client/MqttClientDisconnectedEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace MQTTnet.Core.Client +{ + public class MqttClientDisconnectedEventArgs : EventArgs + { + public MqttClientDisconnectedEventArgs(bool clientWasConnected) + { + ClientWasConnected = clientWasConnected; + } + + public bool ClientWasConnected { get; } + } +} diff --git a/MQTTnet.Core/Client/MqttClientTcpOptions.cs b/MQTTnet.Core/Client/MqttClientTcpOptions.cs index beaa506a..51d2b973 100644 --- a/MQTTnet.Core/Client/MqttClientTcpOptions.cs +++ b/MQTTnet.Core/Client/MqttClientTcpOptions.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Core.Client { - public class MqttClientTcpOptions : MqttClientOptions + public class MqttClientTcpOptions : BaseMqttClientOptions { public string Server { get; set; } diff --git a/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs b/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs index 4b905242..b2b57777 100644 --- a/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs +++ b/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs @@ -1,6 +1,6 @@ namespace MQTTnet.Core.Client { - public class MqttClientWebSocketOptions : MqttClientOptions + public class MqttClientWebSocketOptions : BaseMqttClientOptions { public string Uri { get; set; } } diff --git a/MQTTnet.Core/Exceptions/MqttCommunicationException.cs b/MQTTnet.Core/Exceptions/MqttCommunicationException.cs index 2fc578e9..e019f4ee 100644 --- a/MQTTnet.Core/Exceptions/MqttCommunicationException.cs +++ b/MQTTnet.Core/Exceptions/MqttCommunicationException.cs @@ -17,10 +17,5 @@ namespace MQTTnet.Core.Exceptions : base(message) { } - - public MqttCommunicationException(string message, Exception innerException) - : base(message, innerException) - { - } } } diff --git a/MQTTnet.Core/MQTTnet.Core.csproj b/MQTTnet.Core/MQTTnet.Core.csproj index 0e9b965c..679e4a85 100644 --- a/MQTTnet.Core/MQTTnet.Core.csproj +++ b/MQTTnet.Core/MQTTnet.Core.csproj @@ -32,4 +32,8 @@ + + + + \ No newline at end of file diff --git a/MQTTnet.Core/MqttApplicationMessageFactory.cs b/MQTTnet.Core/MqttApplicationMessageFactory.cs index 55025a31..c5bbee57 100644 --- a/MQTTnet.Core/MqttApplicationMessageFactory.cs +++ b/MQTTnet.Core/MqttApplicationMessageFactory.cs @@ -8,13 +8,6 @@ namespace MQTTnet.Core { public class MqttApplicationMessageFactory { - public MqttApplicationMessage CreateApplicationMessage(string topic, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false) - { - if (topic == null) throw new ArgumentNullException(nameof(topic)); - - return CreateApplicationMessage(topic, new byte[0], qualityOfServiceLevel, retain); - } - public MqttApplicationMessage CreateApplicationMessage(string topic, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false) { if (topic == null) throw new ArgumentNullException(nameof(topic)); diff --git a/MQTTnet.Core/Packets/MqttPacketExtensions.cs b/MQTTnet.Core/Packets/MqttPacketExtensions.cs index fef10543..6a7e0cea 100644 --- a/MQTTnet.Core/Packets/MqttPacketExtensions.cs +++ b/MQTTnet.Core/Packets/MqttPacketExtensions.cs @@ -12,8 +12,7 @@ namespace MQTTnet.Core.Packets if (responsePacket is IMqttPacketWithIdentifier responsePacketWithIdentifier) { - var requestPacketWithIdentifier = packet as IMqttPacketWithIdentifier; - if (requestPacketWithIdentifier == null) + if (!(packet is IMqttPacketWithIdentifier requestPacketWithIdentifier)) { throw new InvalidOperationException("Response packet has PacketIdentifier but request packet does not."); } diff --git a/MQTTnet.Core/Packets/MqttSubAckPacket.cs b/MQTTnet.Core/Packets/MqttSubAckPacket.cs index ee577ae1..5882d4c8 100644 --- a/MQTTnet.Core/Packets/MqttSubAckPacket.cs +++ b/MQTTnet.Core/Packets/MqttSubAckPacket.cs @@ -8,7 +8,7 @@ namespace MQTTnet.Core.Packets { public ushort PacketIdentifier { get; set; } - public IList SubscribeReturnCodes { get; set; } = new List(); + public IList SubscribeReturnCodes { get; } = new List(); public override string ToString() { diff --git a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs b/MQTTnet.Core/Serializer/MqttPacketSerializer.cs index d31a8fe7..833d7986 100644 --- a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs +++ b/MQTTnet.Core/Serializer/MqttPacketSerializer.cs @@ -58,19 +58,19 @@ namespace MQTTnet.Core.Serializer return Serialize(connAckPacket, writer); } - if (packet is MqttDisconnectPacket disconnectPacket) + if (packet is MqttDisconnectPacket) { - return Serialize(disconnectPacket); + return SerializeEmptyPacket(MqttControlPacketType.Disconnect); } - if (packet is MqttPingReqPacket pingReqPacket) + if (packet is MqttPingReqPacket) { - return Serialize(pingReqPacket); + return SerializeEmptyPacket(MqttControlPacketType.PingReq); } - if (packet is MqttPingRespPacket pingRespPacket) + if (packet is MqttPingRespPacket) { - return Serialize(pingRespPacket); + return SerializeEmptyPacket(MqttControlPacketType.PingResp); } if (packet is MqttPublishPacket publishPacket) @@ -338,6 +338,8 @@ namespace MQTTnet.Core.Serializer private static void ValidateConnectPacket(MqttConnectPacket packet) { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession) { throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7]."); @@ -346,6 +348,8 @@ namespace MQTTnet.Core.Serializer private static void ValidatePublishPacket(MqttPublishPacket packet) { + if (packet == null) throw new ArgumentNullException(nameof(packet)); + if (packet.QualityOfServiceLevel == 0 && packet.Dup) { throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2]."); @@ -433,21 +437,6 @@ namespace MQTTnet.Core.Serializer return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02); } - private static byte Serialize(MqttDisconnectPacket packet) - { - return SerializeEmptyPacket(MqttControlPacketType.Disconnect); - } - - private static byte Serialize(MqttPingReqPacket packet) - { - return SerializeEmptyPacket(MqttControlPacketType.PingReq); - } - - private static byte Serialize(MqttPingRespPacket packet) - { - return SerializeEmptyPacket(MqttControlPacketType.PingResp); - } - private static byte Serialize(MqttPublishPacket packet, MqttPacketWriter writer) { ValidatePublishPacket(packet); diff --git a/MQTTnet.Core/Server/IMqttServer.cs b/MQTTnet.Core/Server/IMqttServer.cs index 023feb83..a35a2661 100644 --- a/MQTTnet.Core/Server/IMqttServer.cs +++ b/MQTTnet.Core/Server/IMqttServer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using MQTTnet.Core.Adapter; namespace MQTTnet.Core.Server { @@ -11,7 +10,7 @@ namespace MQTTnet.Core.Server event EventHandler ClientConnected; event EventHandler ClientDisconnected; - IReadOnlyList GetConnectedClients(); + IList GetConnectedClients(); void Publish(MqttApplicationMessage applicationMessage); Task StartAsync(); diff --git a/MQTTnet.Core/Server/MqttClientSessionsManager.cs b/MQTTnet.Core/Server/MqttClientSessionsManager.cs index f78a5417..9c9fbfdc 100644 --- a/MQTTnet.Core/Server/MqttClientSessionsManager.cs +++ b/MQTTnet.Core/Server/MqttClientSessionsManager.cs @@ -103,7 +103,7 @@ namespace MQTTnet.Core.Server } } - public IReadOnlyList GetConnectedClients() + public IList GetConnectedClients() { lock (_clientSessions) { diff --git a/MQTTnet.Core/Server/MqttServer.cs b/MQTTnet.Core/Server/MqttServer.cs index d6f23793..d1615d81 100644 --- a/MQTTnet.Core/Server/MqttServer.cs +++ b/MQTTnet.Core/Server/MqttServer.cs @@ -27,7 +27,7 @@ namespace MQTTnet.Core.Server _clientSessionsManager.ClientDisconnected += OnClientDisconnected; } - public IReadOnlyList GetConnectedClients() + public IList GetConnectedClients() { return _clientSessionsManager.GetConnectedClients(); } diff --git a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs index 2564e69f..2f0966fb 100644 --- a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs +++ b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs @@ -55,7 +55,7 @@ namespace MQTTnet.Core.Tests { try { - var r = await Task.Run(() => + await Task.Run(() => { var iis = new int[0]; return iis[1]; diff --git a/Tests/MQTTnet.Core.Tests/MqttApplicationMessageFactoryTests.cs b/Tests/MQTTnet.Core.Tests/MqttApplicationMessageFactoryTests.cs new file mode 100644 index 00000000..f0f464a0 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/MqttApplicationMessageFactoryTests.cs @@ -0,0 +1,30 @@ +using System; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Core.Protocol; + +namespace MQTTnet.Core.Tests +{ + [TestClass] + public class MqttApplicationMessageFactoryTests + { + [TestMethod] + public void CreateApplicationMessage_TopicOnly() + { + var message = new MqttApplicationMessageFactory().CreateApplicationMessage("Abc", MqttQualityOfServiceLevel.AtLeastOnce); + Assert.AreEqual("Abc", message.Topic); + Assert.IsFalse(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, message.QualityOfServiceLevel); + } + + [TestMethod] + public void CreateApplicationMessage_TimeStampPayload() + { + var message = new MqttApplicationMessageFactory().CreateApplicationMessage("xyz", TimeSpan.FromSeconds(360)); + Assert.AreEqual("xyz", message.Topic); + Assert.IsFalse(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); + Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00"); + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs index c83f83ae..bed32182 100644 --- a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs +++ b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs @@ -12,7 +12,7 @@ namespace MQTTnet.Core.Tests _adapter = adapter; } - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options) + public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options) { return _adapter; } diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index de6265f0..c78e3aee 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -2,10 +2,8 @@ using System.IO; using System.Text; using System.Threading; -using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Core.Adapter; -using MQTTnet.Core.Channel; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; using MQTTnet.Core.Serializer; @@ -387,45 +385,6 @@ namespace MQTTnet.Core.Tests DeserializeAndCompare(p, "sAIAew=="); } - - public class TestChannel : IMqttCommunicationChannel - { - private readonly MemoryStream _stream = new MemoryStream(); - - public Stream ReceiveStream => _stream; - - public Stream RawReceiveStream => _stream; - - public Stream SendStream => _stream; - - public bool IsConnected { get; } = true; - - public TestChannel() - { - } - - public TestChannel(byte[] initialData) - { - _stream.Write(initialData, 0, initialData.Length); - _stream.Position = 0; - } - - public Task ConnectAsync() - { - return Task.FromResult(0); - } - - public Task DisconnectAsync() - { - return Task.FromResult(0); - } - - public byte[] ToArray() - { - return _stream.ToArray(); - } - } - private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) { var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion }; diff --git a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj index 867b0132..41ece1b4 100644 --- a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj +++ b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj @@ -6,6 +6,10 @@ netcoreapp2.0;net45 + + + + diff --git a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index d7060f20..f8b8c33b 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -113,7 +113,8 @@ namespace MQTTnet.TestApp.NetCore .Select(i => CreateMessage()) .ToList(); - if (true) + Console.WriteLine("Press 'c' for concurrent sends. Otherwise in one batch."); + if (Console.ReadKey().KeyChar == 'c') { //send concurrent (test for raceconditions) var sendTasks = msgs @@ -161,10 +162,7 @@ namespace MQTTnet.TestApp.NetCore private static Task PublishSingleMessage(IMqttClient client, MqttApplicationMessage applicationMessage, ref int count) { Interlocked.Increment(ref count); - return Task.Run(() => - { - return client.PublishAsync(applicationMessage); - }); + return Task.Run(() => client.PublishAsync(applicationMessage)); } private static async Task RunServerAsync() diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 31d26dbb..88130cb8 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -6,16 +6,18 @@ using MQTTnet.Core.Protocol; using MQTTnet.Core.Server; using System; using System.Collections.Generic; +using System.IO; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; namespace MQTTnet.TestApp.NetCore { public static class Program { - public static void Main(string[] args) + public static void Main() { Console.WriteLine("MQTTnet - TestApp.NetFramework"); Console.WriteLine("1 = Start client"); @@ -24,12 +26,12 @@ namespace MQTTnet.TestApp.NetCore var pressedKey = Console.ReadKey(true); if (pressedKey.Key == ConsoleKey.D1) { - Task.Run(() => RunClientAsync(args)); + Task.Run(RunClientAsync); Thread.Sleep(Timeout.Infinite); } else if (pressedKey.Key == ConsoleKey.D2) { - Task.Run(() => RunServerAsync(args)); + Task.Run(() => RunServerAsync()); Thread.Sleep(Timeout.Infinite); } else if (pressedKey.Key == ConsoleKey.D3) @@ -39,7 +41,7 @@ namespace MQTTnet.TestApp.NetCore } } - private static async Task RunClientAsync(string[] arguments) + private static async Task RunClientAsync() { MqttNetTrace.TraceMessagePublished += (s, e) => { @@ -128,7 +130,7 @@ namespace MQTTnet.TestApp.NetCore } } - private static void RunServerAsync(string[] arguments) + private static Task RunServerAsync() { MqttNetTrace.TraceMessagePublished += (s, e) => { @@ -157,10 +159,21 @@ namespace MQTTnet.TestApp.NetCore } }; + options.Storage = new RetainedMessageHandler(); + + var certificate = new X509Certificate(@"C:\certs\test\test.cer", ""); options.TlsEndpointOptions.Certificate = certificate.Export(X509ContentType.Cert); + options.ConnectionBacklog = 5; + options.DefaultEndpointOptions.IsEnabled = true; + options.TlsEndpointOptions.IsEnabled = false; var mqttServer = new MqttServerFactory().CreateMqttServer(options); + mqttServer.ClientDisconnected += (s, e) => + { + Console.Write("Client disconnected event fired."); + }; + mqttServer.StartAsync(); Console.WriteLine("Press any key to exit."); @@ -174,6 +187,34 @@ namespace MQTTnet.TestApp.NetCore } Console.ReadLine(); + return Task.FromResult(0); + } + } + + public class RetainedMessageHandler : IMqttServerStorage + { + private const string Filename = "C:\\MQTT\\RetainedMessages.json"; + + public Task SaveRetainedMessagesAsync(IList messages) + { + File.WriteAllText(Filename, JsonConvert.SerializeObject(messages)); + return Task.FromResult(0); + } + + public Task> LoadRetainedMessagesAsync() + { + IList retainedMessages; + if (File.Exists(Filename)) + { + var json = File.ReadAllText(Filename); + retainedMessages = JsonConvert.DeserializeObject>(json); + } + else + { + retainedMessages = new List(); + } + + return Task.FromResult(retainedMessages); } } } diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/App.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/App.xaml.cs index de5a9f87..3ed0757a 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/App.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/App.xaml.cs @@ -1,35 +1,18 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; -using Windows.Foundation; -using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; namespace MQTTnet.TestApp.UniversalWindows { - /// - /// Provides application-specific behavior to supplement the default Application class. - /// - sealed partial class App : Application + sealed partial class App { - /// - /// Initializes the singleton application object. This is the first line of authored code - /// executed, and as such is the logical equivalent of main() or WinMain(). - /// public App() { - this.InitializeComponent(); - this.Suspending += OnSuspending; + InitializeComponent(); + Suspending += OnSuspending; } /// @@ -39,11 +22,9 @@ namespace MQTTnet.TestApp.UniversalWindows /// Details about the launch request and process. protected override void OnLaunched(LaunchActivatedEventArgs e) { - Frame rootFrame = Window.Current.Content as Frame; - // Do not repeat app initialization when the Window already has content, // just ensure that the window is active - if (rootFrame == null) + if (!(Window.Current.Content is Frame rootFrame)) { // Create a Frame to act as the navigation context and navigate to the first page rootFrame = new Frame(); diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml index b097b50a..e2b0a187 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml @@ -58,7 +58,7 @@ QoS: - 0 (At most once) + 0 (At most once) 1 (At least once) 2 (Exactly once) @@ -73,7 +73,7 @@ QoS: - 0 (At most once) + 0 (At most once) 1 (At least once) 2 (Exactly once) diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 07d5955f..de795d55 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -41,7 +41,7 @@ namespace MQTTnet.TestApp.UniversalWindows private async void Connect(object sender, RoutedEventArgs e) { - MqttClientOptions options = null; + BaseMqttClientOptions options = null; if (UseTcp.IsChecked == true) { options = new MqttClientTcpOptions @@ -195,6 +195,8 @@ namespace MQTTnet.TestApp.UniversalWindows } } + // This code is for the Wiki at GitHub! + // ReSharper disable once UnusedMember.Local private async Task WikiCode() { var mqttClient = new MqttClientFactory().CreateMqttClient(); From fe88b7551c36a37af76ef35de8ecfe0da31221e8 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 19 Oct 2017 20:45:37 +0200 Subject: [PATCH 13/34] Refactor managed MQTT client --- .../MqttCommunicationAdapterFactory.cs | 5 - .../MQTTnet.NetStandard/MqttClientFactory.cs | 5 +- .../MqttCommunicationAdapterFactory.cs | 6 - .../MqttClientFactory.cs | 5 +- MQTTnet.Core/Client/IMqttClientFactory.cs | 6 +- MQTTnet.Core/Client/IMqttClientManaged.cs | 9 - MQTTnet.Core/Client/MqttClientManaged .cs | 163 ------------------ .../Client/MqttClientManagedOptions.cs | 12 -- MQTTnet.Core/MQTTnet.Core.csproj | 6 - .../IManagedMqttClientOptions.cs | 15 ++ .../IManagedMqttClientStorage.cs} | 4 +- .../ManagedClient/ManagedMqttClient.cs | 156 +++++++++++++++++ .../ManagedMqttClientMessagesManager.cs} | 18 +- .../ManagedClient/ManagedMqttClientOptions.cs | 15 ++ 14 files changed, 206 insertions(+), 219 deletions(-) delete mode 100644 MQTTnet.Core/Client/IMqttClientManaged.cs delete mode 100644 MQTTnet.Core/Client/MqttClientManaged .cs delete mode 100644 MQTTnet.Core/Client/MqttClientManagedOptions.cs create mode 100644 MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs rename MQTTnet.Core/{Client/IMqttClientQueuedStorage.cs => ManagedClient/IManagedMqttClientStorage.cs} (73%) create mode 100644 MQTTnet.Core/ManagedClient/ManagedMqttClient.cs rename MQTTnet.Core/{Client/MqttClientQueuedPersistentMessagesManager.cs => ManagedClient/ManagedMqttClientMessagesManager.cs} (76%) create mode 100644 MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs index ceaac98b..05e6fb63 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs @@ -21,11 +21,6 @@ namespace MQTTnet.Implementations return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); } - if (options is MqttClientManagedOptions queuedOptions) - { - return new MqttChannelCommunicationAdapter(new MqttTcpChannel(queuedOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); - } - throw new NotSupportedException(); } } diff --git a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs index 53ce2765..09766014 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs @@ -1,4 +1,5 @@ using MQTTnet.Core.Client; +using MQTTnet.Core.ManagedClient; using MQTTnet.Implementations; namespace MQTTnet @@ -10,9 +11,9 @@ namespace MQTTnet return new MqttClient(new MqttCommunicationAdapterFactory()); } - public IMqttClientManaged CreateMqttManagedClient() + public ManagedMqttClient CreateManagedMqttClient() { - return new MqttClientManaged(new MqttCommunicationAdapterFactory()); + return new ManagedMqttClient(new MqttCommunicationAdapterFactory()); } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs index 9e429fd1..05e6fb63 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs @@ -21,12 +21,6 @@ namespace MQTTnet.Implementations return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); } - if (options is MqttClientManagedOptions queuedOptions) - { - return new MqttChannelCommunicationAdapter(new MqttTcpChannel(queuedOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); - } - - throw new NotSupportedException(); } } diff --git a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs b/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs index 53ce2765..09766014 100644 --- a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs +++ b/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs @@ -1,4 +1,5 @@ using MQTTnet.Core.Client; +using MQTTnet.Core.ManagedClient; using MQTTnet.Implementations; namespace MQTTnet @@ -10,9 +11,9 @@ namespace MQTTnet return new MqttClient(new MqttCommunicationAdapterFactory()); } - public IMqttClientManaged CreateMqttManagedClient() + public ManagedMqttClient CreateManagedMqttClient() { - return new MqttClientManaged(new MqttCommunicationAdapterFactory()); + return new ManagedMqttClient(new MqttCommunicationAdapterFactory()); } } } \ No newline at end of file diff --git a/MQTTnet.Core/Client/IMqttClientFactory.cs b/MQTTnet.Core/Client/IMqttClientFactory.cs index 25e03b39..7547906e 100644 --- a/MQTTnet.Core/Client/IMqttClientFactory.cs +++ b/MQTTnet.Core/Client/IMqttClientFactory.cs @@ -1,9 +1,11 @@ -namespace MQTTnet.Core.Client +using MQTTnet.Core.ManagedClient; + +namespace MQTTnet.Core.Client { public interface IMqttClientFactory { IMqttClient CreateMqttClient(); - IMqttClientManaged CreateMqttManagedClient(); + ManagedMqttClient CreateManagedMqttClient(); } } \ No newline at end of file diff --git a/MQTTnet.Core/Client/IMqttClientManaged.cs b/MQTTnet.Core/Client/IMqttClientManaged.cs deleted file mode 100644 index 1fc42f09..00000000 --- a/MQTTnet.Core/Client/IMqttClientManaged.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace MQTTnet.Core.Client -{ - public interface IMqttClientManaged : IMqttClient - { - //Task ConnectAsync(MqttClientManagedOptions options); - } -} diff --git a/MQTTnet.Core/Client/MqttClientManaged .cs b/MQTTnet.Core/Client/MqttClientManaged .cs deleted file mode 100644 index 03afc926..00000000 --- a/MQTTnet.Core/Client/MqttClientManaged .cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Exceptions; -using MQTTnet.Core.Packets; -using MQTTnet.Core.Protocol; -using MQTTnet.Core.Internal; - -namespace MQTTnet.Core.Client -{ - public class MqttClientManaged: IMqttClientManaged - { - private MqttClientManagedOptions _options; - private int _latestPacketIdentifier; - private readonly BlockingCollection _inflightQueue; - private bool _usePersistance = false; - private MqttClientQueuedPersistentMessagesManager _persistentMessagesManager; - private readonly MqttClient _baseMqttClient; - - public MqttClientManaged(IMqttCommunicationAdapterFactory communicationChannelFactory) - { - _baseMqttClient = new MqttClient(communicationChannelFactory); - _baseMqttClient.Connected += BaseMqttClient_Connected; - _baseMqttClient.Disconnected += BaseMqttClient_Disconnected; - _baseMqttClient.ApplicationMessageReceived += BaseMqttClient_ApplicationMessageReceived; - _inflightQueue = new BlockingCollection(); - } - - private void BaseMqttClient_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e) - { - ApplicationMessageReceived?.Invoke(this, e); - } - - private void BaseMqttClient_Disconnected(object sender, EventArgs e) - { - Disconnected?.Invoke(this, e); - } - - private void BaseMqttClient_Connected(object sender, EventArgs e) - { - Connected?.Invoke(this, e); - } - - public event EventHandler Connected; - public event EventHandler Disconnected; - public event EventHandler ApplicationMessageReceived; - - public bool IsConnected => _baseMqttClient.IsConnected; - - - public async Task ConnectAsync(IMqttClientOptions options) - { - //TODO VERY BAD - _options = options as MqttClientManagedOptions; - this._usePersistance = _options.Storage != null; - await _baseMqttClient.ConnectAsync(options); - SetupOutgoingPacketProcessingAsync(); - - //load persistentMessages - if (_usePersistance) - { - if (_persistentMessagesManager == null) - _persistentMessagesManager = new MqttClientQueuedPersistentMessagesManager(_options); - await _persistentMessagesManager.LoadMessagesAsync(); - await InternalPublishAsync(_persistentMessagesManager.GetMessages(), false); - } - } - - public async Task DisconnectAsync() - { - await _baseMqttClient.DisconnectAsync(); - } - - public async Task UnsubscribeAsync(IEnumerable topicFilters) - { - await _baseMqttClient.UnsubscribeAsync(topicFilters); - } - - public async Task PublishAsync(IEnumerable applicationMessages) - { - await InternalPublishAsync(applicationMessages, true); - } - - private async Task InternalPublishAsync(IEnumerable applicationMessages, bool appendIfUsePersistance) - { - ThrowIfNotConnected(); - - foreach (var applicationMessage in applicationMessages) - { - if (_usePersistance && appendIfUsePersistance) - await _persistentMessagesManager.SaveMessageAsync(applicationMessage); - - _inflightQueue.Add(applicationMessage); - } - } - - public async Task> SubscribeAsync(IEnumerable topicFilters) - { - return await _baseMqttClient.SubscribeAsync(topicFilters); - } - - private void ThrowIfNotConnected() - { - if (!IsConnected) throw new MqttCommunicationException("The client is not connected."); - } - - private ushort GetNewPacketIdentifier() - { - return (ushort)Interlocked.Increment(ref _latestPacketIdentifier); - } - - - private void SetupOutgoingPacketProcessingAsync() - { - Task.Factory.StartNew( - () => SendPackets(_baseMqttClient._cancellationTokenSource.Token), - _baseMqttClient._cancellationTokenSource.Token, - TaskCreationOptions.LongRunning, - TaskScheduler.Default).ConfigureAwait(false); - } - - private async Task SendPackets(CancellationToken cancellationToken) - { - MqttNetTrace.Information(nameof(MqttClientManaged), "Start sending packets."); - MqttApplicationMessage messageInQueue = null; - - try - { - while (!cancellationToken.IsCancellationRequested) - { - messageInQueue = _inflightQueue.Take(); - await _baseMqttClient.PublishAsync(new List() { messageInQueue }); - if (_usePersistance) - await _persistentMessagesManager.Remove(messageInQueue); - } - } - catch (OperationCanceledException) - { - } - catch (MqttCommunicationException exception) - { - MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending packets."); - //message not send, equeue it again - if (messageInQueue != null) - _inflightQueue.Add(messageInQueue); - } - catch (Exception exception) - { - MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while sending packets."); - await DisconnectAsync().ConfigureAwait(false); - } - finally - { - MqttNetTrace.Information(nameof(MqttClient), "Stopped sending packets."); - } - } - } -} diff --git a/MQTTnet.Core/Client/MqttClientManagedOptions.cs b/MQTTnet.Core/Client/MqttClientManagedOptions.cs deleted file mode 100644 index 77feabed..00000000 --- a/MQTTnet.Core/Client/MqttClientManagedOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ - -using System; - -namespace MQTTnet.Core.Client -{ - public class MqttClientManagedOptions: MqttClientTcpOptions - { - public bool UseAutoReconnect { get; set; } - public TimeSpan AutoReconnectDelay { get; set; } - public IMqttClientQueuedStorage Storage { get; set; } - } -} diff --git a/MQTTnet.Core/MQTTnet.Core.csproj b/MQTTnet.Core/MQTTnet.Core.csproj index 89fdc9c0..2f55ab5d 100644 --- a/MQTTnet.Core/MQTTnet.Core.csproj +++ b/MQTTnet.Core/MQTTnet.Core.csproj @@ -22,10 +22,4 @@ - - - - - - \ No newline at end of file diff --git a/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs b/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs new file mode 100644 index 00000000..33c91e3e --- /dev/null +++ b/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs @@ -0,0 +1,15 @@ +using System; +using MQTTnet.Core.Client; + +namespace MQTTnet.Core.ManagedClient +{ + public interface IManagedMqttClientOptions + { + IMqttClientOptions ClientOptions { get; } + + bool UseAutoReconnect { get; } + TimeSpan AutoReconnectDelay { get; } + + IManagedMqttClientStorage Storage { get; } + } +} \ No newline at end of file diff --git a/MQTTnet.Core/Client/IMqttClientQueuedStorage.cs b/MQTTnet.Core/ManagedClient/IManagedMqttClientStorage.cs similarity index 73% rename from MQTTnet.Core/Client/IMqttClientQueuedStorage.cs rename to MQTTnet.Core/ManagedClient/IManagedMqttClientStorage.cs index 7909a563..39ea89cc 100644 --- a/MQTTnet.Core/Client/IMqttClientQueuedStorage.cs +++ b/MQTTnet.Core/ManagedClient/IManagedMqttClientStorage.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace MQTTnet.Core.Client +namespace MQTTnet.Core.ManagedClient { - public interface IMqttClientQueuedStorage + public interface IManagedMqttClientStorage { Task SaveQueuedMessagesAsync(IList messages); diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs new file mode 100644 index 00000000..1b86a756 --- /dev/null +++ b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Core.Client; +using MQTTnet.Core.Exceptions; +using MQTTnet.Core.Packets; + +namespace MQTTnet.Core.ManagedClient +{ + public class ManagedMqttClient + { + private readonly List _messageQueue = new List(); + private readonly AutoResetEvent _messageQueueGate = new AutoResetEvent(false); + private readonly MqttClient _mqttClient; + + private IManagedMqttClientOptions _options; + + public ManagedMqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory) + { + if (communicationChannelFactory == null) throw new ArgumentNullException(nameof(communicationChannelFactory)); + + _mqttClient = new MqttClient(communicationChannelFactory); + _mqttClient.Connected += OnConnected; + _mqttClient.Disconnected += OnDisconnected; + _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; + } + + private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e) + { + ApplicationMessageReceived?.Invoke(this, e); + } + + private void OnDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs) + { + //Disconnected?.Invoke(this, e); + } + + private void OnConnected(object sender, EventArgs e) + { + Connected?.Invoke(this, e); + } + + public event EventHandler Connected; + public event EventHandler Disconnected; + public event EventHandler ApplicationMessageReceived; + + public bool IsConnected => _mqttClient.IsConnected; + + + public void Start(IManagedMqttClientOptions options) + { + + } + + public void Stop() + { + + } + + public async Task ConnectAsync(IManagedMqttClientOptions options) + { + //////TODO VERY BAD + ////_options = options as ManagedMqttClientTcpOptions; + ////this._usePersistance = _options.Storage != null; + ////await _mqttClient.ConnectAsync(options); + ////SetupOutgoingPacketProcessingAsync(); + + //////load persistentMessages + ////////if (_usePersistance) + ////////{ + //////// if (_persistentMessagesManager == null) + //////// _persistentMessagesManager = new ManagedMqttClientMessagesManager(_options); + //////// await _persistentMessagesManager.LoadMessagesAsync(); + //////// await InternalPublishAsync(_persistentMessagesManager.GetMessages(), false); + ////////} + } + + public async Task DisconnectAsync() + { + await _mqttClient.DisconnectAsync(); + } + + public async Task UnsubscribeAsync(IEnumerable topicFilters) + { + // TODO: Move all subscriptions to list an subscribe after connection has lost. But only if server session is new. + await _mqttClient.UnsubscribeAsync(topicFilters); + } + + public void Enqueue(IEnumerable applicationMessages) + { + ThrowIfNotConnected(); + + _messageQueue.AddRange(applicationMessages); + _options.Storage?.SaveQueuedMessagesAsync(_messageQueue.ToList()); + + _messageQueueGate.Set(); + } + + public async Task> SubscribeAsync(IEnumerable topicFilters) + { + return await _mqttClient.SubscribeAsync(topicFilters); + } + + private void ThrowIfNotConnected() + { + if (!IsConnected) throw new MqttCommunicationException("The client is not connected."); + } + + private void SetupOutgoingPacketProcessingAsync() + { + //Task.Factory.StartNew( + // () => SendPackets(_mqttClient._cancellationTokenSource.Token), + // _mqttClient._cancellationTokenSource.Token, + // TaskCreationOptions.LongRunning, + // TaskScheduler.Default).ConfigureAwait(false); + } + + private async Task SendPackets(CancellationToken cancellationToken) + { + //MqttNetTrace.Information(nameof(MqttClientManaged), "Start sending packets."); + //MqttApplicationMessage messageInQueue = null; + + //try + //{ + // while (!cancellationToken.IsCancellationRequested) + // { + // messageInQueue = _inflightQueue.Take(); + // await _mqttClient.PublishAsync(new List() { messageInQueue }); + // if (_usePersistance) + // await _persistentMessagesManager.Remove(messageInQueue); + // } + //} + //catch (OperationCanceledException) + //{ + //} + //catch (MqttCommunicationException exception) + //{ + // MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending packets."); + // //message not send, equeue it again + // if (messageInQueue != null) + // _inflightQueue.Add(messageInQueue); + //} + //catch (Exception exception) + //{ + // MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while sending packets."); + // await DisconnectAsync().ConfigureAwait(false); + //} + //finally + //{ + // MqttNetTrace.Information(nameof(MqttClient), "Stopped sending packets."); + //} + } + } +} diff --git a/MQTTnet.Core/Client/MqttClientQueuedPersistentMessagesManager.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClientMessagesManager.cs similarity index 76% rename from MQTTnet.Core/Client/MqttClientQueuedPersistentMessagesManager.cs rename to MQTTnet.Core/ManagedClient/ManagedMqttClientMessagesManager.cs index b012e215..7b667eac 100644 --- a/MQTTnet.Core/Client/MqttClientQueuedPersistentMessagesManager.cs +++ b/MQTTnet.Core/ManagedClient/ManagedMqttClientMessagesManager.cs @@ -1,18 +1,16 @@ -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Packets; -using System; -using System.Linq; +using System; using System.Collections.Generic; using System.Threading.Tasks; +using MQTTnet.Core.Diagnostics; -namespace MQTTnet.Core.Client +namespace MQTTnet.Core.ManagedClient { - public class MqttClientQueuedPersistentMessagesManager + public class ManagedMqttClientMessagesManager { private readonly IList _persistedMessages = new List(); - private readonly MqttClientManagedOptions _options; + private readonly ManagedMqttClientOptions _options; - public MqttClientQueuedPersistentMessagesManager(MqttClientManagedOptions options) + public ManagedMqttClientMessagesManager(ManagedMqttClientOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); } @@ -33,7 +31,7 @@ namespace MQTTnet.Core.Client } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttClientQueuedPersistentMessagesManager), exception, "Unhandled exception while loading persistent messages."); + MqttNetTrace.Error(nameof(ManagedMqttClientMessagesManager), exception, "Unhandled exception while loading persistent messages."); } } @@ -55,7 +53,7 @@ namespace MQTTnet.Core.Client } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttClientQueuedPersistentMessagesManager), exception, "Unhandled exception while saving persistent messages."); + MqttNetTrace.Error(nameof(ManagedMqttClientMessagesManager), exception, "Unhandled exception while saving persistent messages."); } } diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs new file mode 100644 index 00000000..bcddb63a --- /dev/null +++ b/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs @@ -0,0 +1,15 @@ +using System; +using MQTTnet.Core.Client; + +namespace MQTTnet.Core.ManagedClient +{ + public class ManagedMqttClientOptions : IManagedMqttClientOptions + { + public IMqttClientOptions ClientOptions { get; set; } + + public bool UseAutoReconnect { get; set; } = true; + public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5); + + public IManagedMqttClientStorage Storage { get; set; } + } +} From 11ade8c3df9d58c7cdb4ac2b5fd708df7fdc052b Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 20 Oct 2017 23:30:58 +0200 Subject: [PATCH 14/34] Refactor tracing --- Build/MQTTnet.nuspec | 3 + .../MqttCommunicationAdapterFactory.cs | 7 +- .../Implementations/MqttServerAdapter.cs | 14 ++- .../MQTTnet.NetStandard/MqttClientFactory.cs | 9 +- .../MQTTnet.NetStandard/MqttServerFactory.cs | 6 +- .../MqttCommunicationAdapterFactory.cs | 7 +- .../Implementations/MqttServerAdapter.cs | 10 +- .../MqttClientFactory.cs | 9 +- .../MqttServerFactory.cs | 6 +- LICENSE | 2 +- .../MqttChannelCommunicationAdapter.cs | 8 +- MQTTnet.Core/Client/IMqttClientFactory.cs | 7 +- MQTTnet.Core/Client/MqttClient.cs | 42 ++++--- MQTTnet.Core/Client/MqttPacketDispatcher.cs | 8 +- .../Diagnostics/IMqttNetTraceHandler.cs | 9 ++ MQTTnet.Core/Diagnostics/MqttNetTrace.cs | 36 ++++-- .../Diagnostics/MqttNetTraceMessage.cs | 29 +++++ .../MqttNetTraceMessagePublishedEventArgs.cs | 18 +-- .../ManagedClient/ManagedMqttClient.cs | 46 +++++--- .../ManagedMqttClientMessagesManager.cs | 82 ------------- MQTTnet.Core/MqttApplicationMessageBuilder.cs | 108 ++++++++++++++++++ MQTTnet.Core/MqttApplicationMessageFactory.cs | 51 --------- MQTTnet.Core/Server/IMqttServerFactory.cs | 6 +- .../Server/MqttClientPendingMessagesQueue.cs | 12 +- .../MqttClientRetainedMessagesManager.cs | 12 +- MQTTnet.Core/Server/MqttClientSession.cs | 20 ++-- .../Server/MqttClientSessionsManager.cs | 18 +-- MQTTnet.Core/Server/MqttServer.cs | 14 ++- MQTTnet.sln | 1 + README.md | 28 ++++- .../MqttApplicationMessageBuilderTests.cs | 61 ++++++++++ .../MqttApplicationMessageFactoryTests.cs | 30 ----- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 21 ++-- .../TestMqttServerAdapter.cs | 3 +- Tests/MQTTnet.TestApp.NetCore/Program.cs | 69 +++++++++-- .../MainPage.xaml.cs | 6 +- 36 files changed, 497 insertions(+), 321 deletions(-) create mode 100644 MQTTnet.Core/Diagnostics/IMqttNetTraceHandler.cs create mode 100644 MQTTnet.Core/Diagnostics/MqttNetTraceMessage.cs delete mode 100644 MQTTnet.Core/ManagedClient/ManagedMqttClientMessagesManager.cs create mode 100644 MQTTnet.Core/MqttApplicationMessageBuilder.cs delete mode 100644 MQTTnet.Core/MqttApplicationMessageFactory.cs create mode 100644 Tests/MQTTnet.Core.Tests/MqttApplicationMessageBuilderTests.cs delete mode 100644 Tests/MQTTnet.Core.Tests/MqttApplicationMessageFactoryTests.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index ffc3d7f1..128f040e 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -11,6 +11,9 @@ false MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). * [Core] Merged the .NET Framwork and netstandard projects (Thanks to @JanEggers) +* [Core] Migrated the trace to a non-static approach (Breaking Change!) +* [Core] Added a builder for application messages using a fluent API +* [Client] Added a first version of a managed client which will manage the connection, subscription etc. automatically (Thanks to @JTrotta) Copyright Christian Kratky 2016-2017 MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs index 05e6fb63..0654e27b 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs @@ -1,6 +1,7 @@ using System; using MQTTnet.Core.Adapter; using MQTTnet.Core.Client; +using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Serializer; namespace MQTTnet.Implementations @@ -13,12 +14,14 @@ namespace MQTTnet.Implementations if (options is MqttClientTcpOptions tcpOptions) { - return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); + var trace = new MqttNetTrace(); + return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }, trace); } if (options is MqttClientWebSocketOptions webSocketOptions) { - return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); + var trace = new MqttNetTrace(); + return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }, trace); } throw new NotSupportedException(); diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs index b035d89d..4b0c178c 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs @@ -15,11 +15,17 @@ namespace MQTTnet.Implementations { public class MqttServerAdapter : IMqttServerAdapter, IDisposable { + private readonly MqttNetTrace _trace; private CancellationTokenSource _cancellationTokenSource; private Socket _defaultEndpointSocket; private Socket _tlsEndpointSocket; private X509Certificate2 _tlsCertificate; + public MqttServerAdapter(MqttNetTrace trace) + { + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); + } + public event EventHandler ClientAccepted; public Task StartAsync(MqttServerOptions options) @@ -94,12 +100,12 @@ namespace MQTTnet.Implementations #else var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false); #endif - var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer()); + var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _trace); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint."); + _trace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint."); //excessive CPU consumed if in endless loop of socket errors await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); @@ -122,12 +128,12 @@ namespace MQTTnet.Implementations var sslStream = new SslStream(new NetworkStream(clientSocket)); await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); - var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer()); + var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _trace); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint."); + _trace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint."); //excessive CPU consumed if in endless loop of socket errors await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); diff --git a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs index 09766014..fd5da681 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs @@ -1,4 +1,5 @@ using MQTTnet.Core.Client; +using MQTTnet.Core.Diagnostics; using MQTTnet.Core.ManagedClient; using MQTTnet.Implementations; @@ -6,14 +7,14 @@ namespace MQTTnet { public class MqttClientFactory : IMqttClientFactory { - public IMqttClient CreateMqttClient() + public IMqttClient CreateMqttClient(IMqttNetTraceHandler traceHandler = null) { - return new MqttClient(new MqttCommunicationAdapterFactory()); + return new MqttClient(new MqttCommunicationAdapterFactory(), new MqttNetTrace(traceHandler)); } - public ManagedMqttClient CreateManagedMqttClient() + public ManagedMqttClient CreateManagedMqttClient(IMqttNetTraceHandler traceHandler = null) { - return new ManagedMqttClient(new MqttCommunicationAdapterFactory()); + return new ManagedMqttClient(new MqttCommunicationAdapterFactory(), new MqttNetTrace()); } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs index 70e14f3c..468b8e08 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using MQTTnet.Core.Adapter; +using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Server; using MQTTnet.Implementations; @@ -8,11 +9,12 @@ namespace MQTTnet { public class MqttServerFactory : IMqttServerFactory { - public IMqttServer CreateMqttServer(MqttServerOptions options) + public IMqttServer CreateMqttServer(MqttServerOptions options, IMqttNetTraceHandler traceHandler = null) { if (options == null) throw new ArgumentNullException(nameof(options)); - return new MqttServer(options, new List { new MqttServerAdapter() }); + var trace = new MqttNetTrace(traceHandler); + return new MqttServer(options, new List { new MqttServerAdapter(trace) }, trace); } } } diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs index 05e6fb63..0654e27b 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs @@ -1,6 +1,7 @@ using System; using MQTTnet.Core.Adapter; using MQTTnet.Core.Client; +using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Serializer; namespace MQTTnet.Implementations @@ -13,12 +14,14 @@ namespace MQTTnet.Implementations if (options is MqttClientTcpOptions tcpOptions) { - return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); + var trace = new MqttNetTrace(); + return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }, trace); } if (options is MqttClientWebSocketOptions webSocketOptions) { - return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }); + var trace = new MqttNetTrace(); + return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }, trace); } throw new NotSupportedException(); diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs index f33dd593..f5963186 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs @@ -10,8 +10,14 @@ namespace MQTTnet.Implementations { public class MqttServerAdapter : IMqttServerAdapter, IDisposable { + private readonly MqttNetTrace _trace; private StreamSocketListener _defaultEndpointSocket; + public MqttServerAdapter(MqttNetTrace trace) + { + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); + } + public event EventHandler ClientAccepted; public async Task StartAsync(MqttServerOptions options) @@ -50,12 +56,12 @@ namespace MQTTnet.Implementations { try { - var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer()); + var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer(), _trace); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint."); + _trace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint."); } } } diff --git a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs b/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs index 09766014..9a254f20 100644 --- a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs +++ b/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs @@ -1,4 +1,5 @@ using MQTTnet.Core.Client; +using MQTTnet.Core.Diagnostics; using MQTTnet.Core.ManagedClient; using MQTTnet.Implementations; @@ -6,14 +7,14 @@ namespace MQTTnet { public class MqttClientFactory : IMqttClientFactory { - public IMqttClient CreateMqttClient() + public IMqttClient CreateMqttClient(IMqttNetTraceHandler traceHandler = null) { - return new MqttClient(new MqttCommunicationAdapterFactory()); + return new MqttClient(new MqttCommunicationAdapterFactory(), new MqttNetTrace(traceHandler)); } - public ManagedMqttClient CreateManagedMqttClient() + public ManagedMqttClient CreateManagedMqttClient(IMqttNetTraceHandler traceHandler = null) { - return new ManagedMqttClient(new MqttCommunicationAdapterFactory()); + return new ManagedMqttClient(new MqttCommunicationAdapterFactory(), new MqttNetTrace(traceHandler)); } } } \ No newline at end of file diff --git a/Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs b/Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs index 70e14f3c..468b8e08 100644 --- a/Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs +++ b/Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using MQTTnet.Core.Adapter; +using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Server; using MQTTnet.Implementations; @@ -8,11 +9,12 @@ namespace MQTTnet { public class MqttServerFactory : IMqttServerFactory { - public IMqttServer CreateMqttServer(MqttServerOptions options) + public IMqttServer CreateMqttServer(MqttServerOptions options, IMqttNetTraceHandler traceHandler = null) { if (options == null) throw new ArgumentNullException(nameof(options)); - return new MqttServer(options, new List { new MqttServerAdapter() }); + var trace = new MqttNetTrace(traceHandler); + return new MqttServer(options, new List { new MqttServerAdapter(trace) }, trace); } } } diff --git a/LICENSE b/LICENSE index 14484d8f..7d84e2b1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Christian +Copyright (c) 2017 Christian Kratky Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs b/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs index 7fae37b0..2bed87d8 100644 --- a/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs +++ b/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs @@ -14,11 +14,13 @@ namespace MQTTnet.Core.Adapter { public class MqttChannelCommunicationAdapter : IMqttCommunicationAdapter { + private readonly MqttNetTrace _trace; private readonly IMqttCommunicationChannel _channel; private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); - public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer) + public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer, MqttNetTrace trace) { + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); _channel = channel ?? throw new ArgumentNullException(nameof(channel)); PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } @@ -94,7 +96,7 @@ namespace MQTTnet.Core.Adapter continue; } - MqttNetTrace.Information(nameof(MqttChannelCommunicationAdapter), "TX >>> {0} [Timeout={1}]", packet, timeout); + _trace.Information(nameof(MqttChannelCommunicationAdapter), "TX >>> {0} [Timeout={1}]", packet, timeout); var writeBuffer = PacketSerializer.Serialize(packet); await _channel.SendStream.WriteAsync(writeBuffer, 0, writeBuffer.Length, cancellationToken).ConfigureAwait(false); @@ -160,7 +162,7 @@ namespace MQTTnet.Core.Adapter throw new MqttProtocolViolationException("Received malformed packet."); } - MqttNetTrace.Information(nameof(MqttChannelCommunicationAdapter), "RX <<< {0}", packet); + _trace.Information(nameof(MqttChannelCommunicationAdapter), "RX <<< {0}", packet); return packet; } catch (TaskCanceledException) diff --git a/MQTTnet.Core/Client/IMqttClientFactory.cs b/MQTTnet.Core/Client/IMqttClientFactory.cs index 7547906e..e63047a8 100644 --- a/MQTTnet.Core/Client/IMqttClientFactory.cs +++ b/MQTTnet.Core/Client/IMqttClientFactory.cs @@ -1,11 +1,12 @@ -using MQTTnet.Core.ManagedClient; +using MQTTnet.Core.Diagnostics; +using MQTTnet.Core.ManagedClient; namespace MQTTnet.Core.Client { public interface IMqttClientFactory { - IMqttClient CreateMqttClient(); + IMqttClient CreateMqttClient(IMqttNetTraceHandler traceHandler = null); - ManagedMqttClient CreateManagedMqttClient(); + ManagedMqttClient CreateManagedMqttClient(IMqttNetTraceHandler traceHandler = null); } } \ No newline at end of file diff --git a/MQTTnet.Core/Client/MqttClient.cs b/MQTTnet.Core/Client/MqttClient.cs index 53857900..4e595d7f 100644 --- a/MQTTnet.Core/Client/MqttClient.cs +++ b/MQTTnet.Core/Client/MqttClient.cs @@ -15,8 +15,9 @@ namespace MQTTnet.Core.Client public class MqttClient : IMqttClient { private readonly HashSet _unacknowledgedPublishPackets = new HashSet(); - private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); + private readonly MqttPacketDispatcher _packetDispatcher; private readonly IMqttCommunicationAdapterFactory _communicationChannelFactory; + private readonly MqttNetTrace _trace; private IMqttClientOptions _options; private bool _isReceivingPackets; @@ -24,9 +25,12 @@ namespace MQTTnet.Core.Client private CancellationTokenSource _cancellationTokenSource; private IMqttCommunicationAdapter _adapter; - public MqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory) + public MqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory, MqttNetTrace trace) { + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); _communicationChannelFactory = communicationChannelFactory ?? throw new ArgumentNullException(nameof(communicationChannelFactory)); + + _packetDispatcher = new MqttPacketDispatcher(trace); } public event EventHandler Connected; @@ -49,15 +53,15 @@ namespace MQTTnet.Core.Client _packetDispatcher.Reset(); _adapter = _communicationChannelFactory.CreateMqttCommunicationAdapter(options); - - MqttNetTrace.Verbose(nameof(MqttClient), "Trying to connect with server."); + + _trace.Verbose(nameof(MqttClient), "Trying to connect with server."); await _adapter.ConnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); - MqttNetTrace.Verbose(nameof(MqttClient), "Connection with server established."); + _trace.Verbose(nameof(MqttClient), "Connection with server established."); await SetupIncomingPacketProcessingAsync(); await AuthenticateAsync(options.WillMessage); - MqttNetTrace.Verbose(nameof(MqttClient), "MQTT connection with server established."); + _trace.Verbose(nameof(MqttClient), "MQTT connection with server established."); if (_options.KeepAlivePeriod != TimeSpan.Zero) { @@ -239,11 +243,11 @@ namespace MQTTnet.Core.Client try { await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); - MqttNetTrace.Information(nameof(MqttClient), "Disconnected from adapter."); + _trace.Information(nameof(MqttClient), "Disconnected from adapter."); } catch (Exception exception) { - MqttNetTrace.Warning(nameof(MqttClient), exception, "Error while disconnecting from adapter."); + _trace.Warning(nameof(MqttClient), exception, "Error while disconnecting from adapter."); } finally { @@ -255,7 +259,7 @@ namespace MQTTnet.Core.Client { try { - MqttNetTrace.Information(nameof(MqttClient), "Received <<< {0}", packet); + _trace.Information(nameof(MqttClient), "Received <<< {0}", packet); if (packet is MqttPingReqPacket) { @@ -285,7 +289,7 @@ namespace MQTTnet.Core.Client } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while processing received packet."); + _trace.Error(nameof(MqttClient), exception, "Unhandled exception while processing received packet."); } } @@ -298,7 +302,7 @@ namespace MQTTnet.Core.Client } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while handling application message."); + _trace.Error(nameof(MqttClient), exception, "Unhandled exception while handling application message."); } } @@ -362,7 +366,7 @@ namespace MQTTnet.Core.Client private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken) { - MqttNetTrace.Information(nameof(MqttClient), "Start sending keep alive packets."); + _trace.Information(nameof(MqttClient), "Start sending keep alive packets."); try { @@ -387,23 +391,23 @@ namespace MQTTnet.Core.Client return; } - MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending/receiving keep alive packets."); + _trace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending/receiving keep alive packets."); await DisconnectInternalAsync().ConfigureAwait(false); } catch (Exception exception) { - MqttNetTrace.Warning(nameof(MqttClient), exception, "Unhandled exception while sending/receiving keep alive packets."); + _trace.Warning(nameof(MqttClient), exception, "Unhandled exception while sending/receiving keep alive packets."); await DisconnectInternalAsync().ConfigureAwait(false); } finally { - MqttNetTrace.Information(nameof(MqttClient), "Stopped sending keep alive packets."); + _trace.Information(nameof(MqttClient), "Stopped sending keep alive packets."); } } private async Task ReceivePackets(CancellationToken cancellationToken) { - MqttNetTrace.Information(nameof(MqttClient), "Start receiving packets."); + _trace.Information(nameof(MqttClient), "Start receiving packets."); try { @@ -430,17 +434,17 @@ namespace MQTTnet.Core.Client return; } - MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets."); + _trace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets."); await DisconnectInternalAsync().ConfigureAwait(false); } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets."); + _trace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets."); await DisconnectInternalAsync().ConfigureAwait(false); } finally { - MqttNetTrace.Information(nameof(MqttClient), "Stopped receiving packets."); + _trace.Information(nameof(MqttClient), "Stopped receiving packets."); } } diff --git a/MQTTnet.Core/Client/MqttPacketDispatcher.cs b/MQTTnet.Core/Client/MqttPacketDispatcher.cs index 484012e4..40902dbc 100644 --- a/MQTTnet.Core/Client/MqttPacketDispatcher.cs +++ b/MQTTnet.Core/Client/MqttPacketDispatcher.cs @@ -12,6 +12,12 @@ namespace MQTTnet.Core.Client { private readonly ConcurrentDictionary> _packetByResponseType = new ConcurrentDictionary>(); private readonly ConcurrentDictionary>> _packetByResponseTypeAndIdentifier = new ConcurrentDictionary>>(); + private readonly MqttNetTrace _trace; + + public MqttPacketDispatcher(MqttNetTrace trace) + { + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); + } public async Task WaitForPacketAsync(MqttBasePacket request, Type responseType, TimeSpan timeout) { @@ -24,7 +30,7 @@ namespace MQTTnet.Core.Client } catch (MqttCommunicationTimedOutException) { - MqttNetTrace.Warning(nameof(MqttPacketDispatcher), "Timeout while waiting for packet of type '{0}'.", responseType.Name); + _trace.Warning(nameof(MqttPacketDispatcher), "Timeout while waiting for packet of type '{0}'.", responseType.Name); throw; } finally diff --git a/MQTTnet.Core/Diagnostics/IMqttNetTraceHandler.cs b/MQTTnet.Core/Diagnostics/IMqttNetTraceHandler.cs new file mode 100644 index 00000000..969cce08 --- /dev/null +++ b/MQTTnet.Core/Diagnostics/IMqttNetTraceHandler.cs @@ -0,0 +1,9 @@ +namespace MQTTnet.Core.Diagnostics +{ + public interface IMqttNetTraceHandler + { + bool IsEnabled { get; } + + void HandleTraceMessage(MqttNetTraceMessage traceMessage); + } +} diff --git a/MQTTnet.Core/Diagnostics/MqttNetTrace.cs b/MQTTnet.Core/Diagnostics/MqttNetTrace.cs index 4ed4619c..35a828cb 100644 --- a/MQTTnet.Core/Diagnostics/MqttNetTrace.cs +++ b/MQTTnet.Core/Diagnostics/MqttNetTrace.cs @@ -2,48 +2,62 @@ namespace MQTTnet.Core.Diagnostics { - public static class MqttNetTrace + public sealed class MqttNetTrace : IMqttNetTraceHandler { + private readonly IMqttNetTraceHandler _traceHandler; + + public MqttNetTrace(IMqttNetTraceHandler traceHandler = null) + { + _traceHandler = traceHandler ?? this; + } + public static event EventHandler TraceMessagePublished; - public static void Verbose(string source, string message, params object[] parameters) + public bool IsEnabled => TraceMessagePublished != null; + + public void Verbose(string source, string message, params object[] parameters) { Publish(source, MqttNetTraceLevel.Verbose, null, message, parameters); } - public static void Information(string source, string message, params object[] parameters) + public void Information(string source, string message, params object[] parameters) { Publish(source, MqttNetTraceLevel.Information, null, message, parameters); } - public static void Warning(string source, string message, params object[] parameters) + public void Warning(string source, string message, params object[] parameters) { Publish(source, MqttNetTraceLevel.Warning, null, message, parameters); } - public static void Warning(string source, Exception exception, string message, params object[] parameters) + public void Warning(string source, Exception exception, string message, params object[] parameters) { Publish(source, MqttNetTraceLevel.Warning, exception, message, parameters); } - public static void Error(string source, string message, params object[] parameters) + public void Error(string source, string message, params object[] parameters) { Publish(source, MqttNetTraceLevel.Error, null, message, parameters); } - public static void Error(string source, Exception exception, string message, params object[] parameters) + public void Error(string source, Exception exception, string message, params object[] parameters) { Publish(source, MqttNetTraceLevel.Error, exception, message, parameters); } - private static void Publish(string source, MqttNetTraceLevel traceLevel, Exception exception, string message, params object[] parameters) + public void HandleTraceMessage(MqttNetTraceMessage mqttNetTraceMessage) { - var handler = TraceMessagePublished; - if (handler == null) + TraceMessagePublished?.Invoke(this, new MqttNetTraceMessagePublishedEventArgs(mqttNetTraceMessage)); + } + + private void Publish(string source, MqttNetTraceLevel traceLevel, Exception exception, string message, params object[] parameters) + { + if (!_traceHandler.IsEnabled) { return; } + var now = DateTime.Now; if (parameters?.Length > 0) { try @@ -57,7 +71,7 @@ namespace MQTTnet.Core.Diagnostics } } - handler.Invoke(null, new MqttNetTraceMessagePublishedEventArgs(Environment.CurrentManagedThreadId, source, traceLevel, message, exception)); + _traceHandler.HandleTraceMessage(new MqttNetTraceMessage(now, Environment.CurrentManagedThreadId, source, traceLevel, message, exception)); } } } diff --git a/MQTTnet.Core/Diagnostics/MqttNetTraceMessage.cs b/MQTTnet.Core/Diagnostics/MqttNetTraceMessage.cs new file mode 100644 index 00000000..3eb1b568 --- /dev/null +++ b/MQTTnet.Core/Diagnostics/MqttNetTraceMessage.cs @@ -0,0 +1,29 @@ +using System; + +namespace MQTTnet.Core.Diagnostics +{ + public sealed class MqttNetTraceMessage + { + public MqttNetTraceMessage(DateTime timestamp, int threadId, string source, MqttNetTraceLevel level, string message, Exception exception) + { + Timestamp = timestamp; + ThreadId = threadId; + Source = source; + Level = level; + Message = message; + Exception = exception; + } + + public DateTime Timestamp { get; } + + public int ThreadId { get; } + + public string Source { get; } + + public MqttNetTraceLevel Level { get; } + + public string Message { get; } + + public Exception Exception { get; } + } +} diff --git a/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs b/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs index 05d53962..5b8f796d 100644 --- a/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs +++ b/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs @@ -4,23 +4,11 @@ namespace MQTTnet.Core.Diagnostics { public sealed class MqttNetTraceMessagePublishedEventArgs : EventArgs { - public MqttNetTraceMessagePublishedEventArgs(int threadId, string source, MqttNetTraceLevel level, string message, Exception exception) + public MqttNetTraceMessagePublishedEventArgs(MqttNetTraceMessage traceMessage) { - ThreadId = threadId; - Source = source; - Level = level; - Message = message; - Exception = exception; + TraceMessage = traceMessage ?? throw new ArgumentNullException(nameof(traceMessage)); } - public int ThreadId { get; } - - public string Source { get; } - - public MqttNetTraceLevel Level { get; } - - public string Message { get; } - - public Exception Exception { get; } + public MqttNetTraceMessage TraceMessage { get; } } } diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs index 1b86a756..989d1176 100644 --- a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs +++ b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Client; +using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Exceptions; using MQTTnet.Core.Packets; @@ -14,36 +15,23 @@ namespace MQTTnet.Core.ManagedClient private readonly List _messageQueue = new List(); private readonly AutoResetEvent _messageQueueGate = new AutoResetEvent(false); private readonly MqttClient _mqttClient; + private readonly MqttNetTrace _trace; private IManagedMqttClientOptions _options; - public ManagedMqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory) + public ManagedMqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory, MqttNetTrace trace) { if (communicationChannelFactory == null) throw new ArgumentNullException(nameof(communicationChannelFactory)); + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); - _mqttClient = new MqttClient(communicationChannelFactory); + _mqttClient = new MqttClient(communicationChannelFactory, _trace); _mqttClient.Connected += OnConnected; _mqttClient.Disconnected += OnDisconnected; _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; } - private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e) - { - ApplicationMessageReceived?.Invoke(this, e); - } - - private void OnDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs) - { - //Disconnected?.Invoke(this, e); - } - - private void OnConnected(object sender, EventArgs e) - { - Connected?.Invoke(this, e); - } - public event EventHandler Connected; - public event EventHandler Disconnected; + public event EventHandler Disconnected; public event EventHandler ApplicationMessageReceived; public bool IsConnected => _mqttClient.IsConnected; @@ -51,7 +39,10 @@ namespace MQTTnet.Core.ManagedClient public void Start(IManagedMqttClientOptions options) { - + if (options == null) throw new ArgumentNullException(nameof(options)); + if (options.ClientOptions == null) throw new ArgumentException("The client options are not set.", nameof(options)); + + } public void Stop() @@ -90,6 +81,8 @@ namespace MQTTnet.Core.ManagedClient public void Enqueue(IEnumerable applicationMessages) { + if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); + ThrowIfNotConnected(); _messageQueue.AddRange(applicationMessages); @@ -152,5 +145,20 @@ namespace MQTTnet.Core.ManagedClient // MqttNetTrace.Information(nameof(MqttClient), "Stopped sending packets."); //} } + + private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs) + { + ApplicationMessageReceived?.Invoke(this, eventArgs); + } + + private void OnDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs) + { + Disconnected?.Invoke(this, eventArgs); + } + + private void OnConnected(object sender, EventArgs eventArgs) + { + Connected?.Invoke(this, eventArgs); + } } } diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClientMessagesManager.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClientMessagesManager.cs deleted file mode 100644 index 7b667eac..00000000 --- a/MQTTnet.Core/ManagedClient/ManagedMqttClientMessagesManager.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using MQTTnet.Core.Diagnostics; - -namespace MQTTnet.Core.ManagedClient -{ - public class ManagedMqttClientMessagesManager - { - private readonly IList _persistedMessages = new List(); - private readonly ManagedMqttClientOptions _options; - - public ManagedMqttClientMessagesManager(ManagedMqttClientOptions options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - public async Task LoadMessagesAsync() - { - try - { - var persistentMessages = await _options.Storage.LoadQueuedMessagesAsync(); - lock (_persistedMessages) - { - _persistedMessages.Clear(); - foreach (var persistentMessage in persistentMessages) - { - _persistedMessages.Add(persistentMessage); - } - } - } - catch (Exception exception) - { - MqttNetTrace.Error(nameof(ManagedMqttClientMessagesManager), exception, "Unhandled exception while loading persistent messages."); - } - } - - public async Task SaveMessageAsync(MqttApplicationMessage applicationMessage) - { - if (applicationMessage != null) - { - lock (_persistedMessages) - { - _persistedMessages.Add(applicationMessage); - } - } - try - { - if (_options.Storage != null) - { - await _options.Storage.SaveQueuedMessagesAsync(_persistedMessages); - } - } - catch (Exception exception) - { - MqttNetTrace.Error(nameof(ManagedMqttClientMessagesManager), exception, "Unhandled exception while saving persistent messages."); - } - } - - public List GetMessages() - { - var persistedMessages = new List(); - lock (_persistedMessages) - { - foreach (var persistedMessage in _persistedMessages) - { - persistedMessages.Add(persistedMessage); - } - } - - return persistedMessages; - } - - public async Task Remove(MqttApplicationMessage message) - { - lock (_persistedMessages) - _persistedMessages.Remove(message); - - await SaveMessageAsync(null); - } - } -} diff --git a/MQTTnet.Core/MqttApplicationMessageBuilder.cs b/MQTTnet.Core/MqttApplicationMessageBuilder.cs new file mode 100644 index 00000000..9905b098 --- /dev/null +++ b/MQTTnet.Core/MqttApplicationMessageBuilder.cs @@ -0,0 +1,108 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using MQTTnet.Core.Exceptions; +using MQTTnet.Core.Protocol; + +namespace MQTTnet.Core +{ + public class MqttApplicationMessageBuilder + { + private MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; + private string _topic; + private byte[] _payload; + private bool _retain; + + public MqttApplicationMessageBuilder WithTopic(string topic) + { + _topic = topic; + return this; + } + + public MqttApplicationMessageBuilder WithPayload(IEnumerable payload) + { + if (payload == null) + { + _payload = null; + return this; + } + + _payload = payload.ToArray(); + return this; + } + + public MqttApplicationMessageBuilder WithPayload(MemoryStream payload) + { + if (payload == null) + { + _payload = null; + return this; + } + + if (payload.Length == 0) + { + _payload = new byte[0]; + } + else + { + _payload = new byte[payload.Length - payload.Position]; + payload.Read(_payload, 0, _payload.Length); + } + + return this; + } + + public MqttApplicationMessageBuilder WithPayload(string payload) + { + if (payload == null) + { + _payload = null; + return this; + } + + _payload = string.IsNullOrEmpty(payload) ? new byte[0] : Encoding.UTF8.GetBytes(payload); + return this; + } + + public MqttApplicationMessageBuilder WithQualityOfServiceLevel(MqttQualityOfServiceLevel qualityOfServiceLevel) + { + _qualityOfServiceLevel = qualityOfServiceLevel; + return this; + } + + public MqttApplicationMessageBuilder WithRetainFlag(bool value = true) + { + _retain = value; + return this; + } + + public MqttApplicationMessageBuilder WithAtLeastOnceQoS() + { + _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce; + return this; + } + + public MqttApplicationMessageBuilder WithAtMostOnceQoS() + { + _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; + return this; + } + + public MqttApplicationMessageBuilder WithExactlyOnceQoS() + { + _qualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce; + return this; + } + + public MqttApplicationMessage Build() + { + if (string.IsNullOrEmpty(_topic)) + { + throw new MqttProtocolViolationException("Topic is not set."); + } + + return new MqttApplicationMessage(_topic, _payload ?? new byte[0], _qualityOfServiceLevel, _retain); + } + } +} diff --git a/MQTTnet.Core/MqttApplicationMessageFactory.cs b/MQTTnet.Core/MqttApplicationMessageFactory.cs deleted file mode 100644 index c5bbee57..00000000 --- a/MQTTnet.Core/MqttApplicationMessageFactory.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Text; -using MQTTnet.Core.Protocol; - -namespace MQTTnet.Core -{ - public class MqttApplicationMessageFactory - { - public MqttApplicationMessage CreateApplicationMessage(string topic, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false) - { - if (topic == null) throw new ArgumentNullException(nameof(topic)); - - if (payload == null) - { - payload = new byte[0]; - } - - return new MqttApplicationMessage(topic, payload, qualityOfServiceLevel, retain); - } - - public MqttApplicationMessage CreateApplicationMessage(string topic, TPayload payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false) - { - if (topic == null) throw new ArgumentNullException(nameof(topic)); - - var payloadString = Convert.ToString(payload, CultureInfo.InvariantCulture); - var payloadBuffer = string.IsNullOrEmpty(payloadString) ? new byte[0] : Encoding.UTF8.GetBytes(payloadString); - - return CreateApplicationMessage(topic, payloadBuffer, qualityOfServiceLevel, retain); - } - - public MqttApplicationMessage CreateApplicationMessage(string topic, Stream payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false) - { - if (topic == null) throw new ArgumentNullException(nameof(topic)); - - byte[] payloadBuffer; - if (payload == null || payload.Length == 0) - { - payloadBuffer = new byte[0]; - } - else - { - payloadBuffer = new byte[payload.Length - payload.Position]; - payload.Read(payloadBuffer, 0, payloadBuffer.Length); - } - - return CreateApplicationMessage(topic, payloadBuffer, qualityOfServiceLevel, retain); - } - } -} diff --git a/MQTTnet.Core/Server/IMqttServerFactory.cs b/MQTTnet.Core/Server/IMqttServerFactory.cs index 4d33a920..865080b3 100644 --- a/MQTTnet.Core/Server/IMqttServerFactory.cs +++ b/MQTTnet.Core/Server/IMqttServerFactory.cs @@ -1,7 +1,9 @@ -namespace MQTTnet.Core.Server +using MQTTnet.Core.Diagnostics; + +namespace MQTTnet.Core.Server { public interface IMqttServerFactory { - IMqttServer CreateMqttServer(MqttServerOptions options); + IMqttServer CreateMqttServer(MqttServerOptions options, IMqttNetTraceHandler traceHandler = null); } } \ No newline at end of file diff --git a/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs b/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs index 6571a758..551dc8e7 100644 --- a/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs +++ b/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs @@ -15,9 +15,11 @@ namespace MQTTnet.Core.Server private readonly BlockingCollection _pendingPublishPackets = new BlockingCollection(); private readonly MqttClientSession _session; private readonly MqttServerOptions _options; + private readonly MqttNetTrace _trace; - public MqttClientPendingMessagesQueue(MqttServerOptions options, MqttClientSession session) + public MqttClientPendingMessagesQueue(MqttServerOptions options, MqttClientSession session, MqttNetTrace trace) { + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); _session = session ?? throw new ArgumentNullException(nameof(session)); _options = options ?? throw new ArgumentNullException(nameof(options)); } @@ -50,7 +52,7 @@ namespace MQTTnet.Core.Server } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Unhandled exception while sending pending publish packets."); + _trace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Unhandled exception while sending pending publish packets."); } } @@ -66,18 +68,18 @@ namespace MQTTnet.Core.Server { if (exception is MqttCommunicationTimedOutException) { - MqttNetTrace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to timeout."); + _trace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to timeout."); } else if (exception is MqttCommunicationException) { - MqttNetTrace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to communication exception."); + _trace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to communication exception."); } if (exception is OperationCanceledException) { } else { - MqttNetTrace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed."); + _trace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed."); } if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) diff --git a/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs b/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs index 77b49bf7..fe95f019 100644 --- a/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs +++ b/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs @@ -10,11 +10,13 @@ namespace MQTTnet.Core.Server { public sealed class MqttClientRetainedMessagesManager { + private readonly MqttNetTrace _trace; private readonly Dictionary _retainedMessages = new Dictionary(); private readonly MqttServerOptions _options; - public MqttClientRetainedMessagesManager(MqttServerOptions options) + public MqttClientRetainedMessagesManager(MqttServerOptions options, MqttNetTrace trace) { + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); _options = options ?? throw new ArgumentNullException(nameof(options)); } @@ -39,7 +41,7 @@ namespace MQTTnet.Core.Server } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while loading retained messages."); + _trace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while loading retained messages."); } } @@ -53,12 +55,12 @@ namespace MQTTnet.Core.Server if (publishPacket.Payload?.Any() == false) { _retainedMessages.Remove(publishPacket.Topic); - MqttNetTrace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' cleared retained message for topic '{1}'.", clientId, publishPacket.Topic); + _trace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' cleared retained message for topic '{1}'.", clientId, publishPacket.Topic); } else { _retainedMessages[publishPacket.Topic] = publishPacket; - MqttNetTrace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' updated retained message for topic '{1}'.", clientId, publishPacket.Topic); + _trace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' updated retained message for topic '{1}'.", clientId, publishPacket.Topic); } allRetainedMessages = new List(_retainedMessages.Values); @@ -74,7 +76,7 @@ namespace MQTTnet.Core.Server } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while saving retained messages."); + _trace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while saving retained messages."); } } diff --git a/MQTTnet.Core/Server/MqttClientSession.cs b/MQTTnet.Core/Server/MqttClientSession.cs index 37d168ae..9b6cce59 100644 --- a/MQTTnet.Core/Server/MqttClientSession.cs +++ b/MQTTnet.Core/Server/MqttClientSession.cs @@ -20,17 +20,19 @@ namespace MQTTnet.Core.Server private readonly MqttClientSessionsManager _mqttClientSessionsManager; private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue; private readonly MqttServerOptions _options; + private readonly MqttNetTrace _trace; private IMqttCommunicationAdapter _adapter; private CancellationTokenSource _cancellationTokenSource; private MqttApplicationMessage _willMessage; - public MqttClientSession(string clientId, MqttServerOptions options, MqttClientSessionsManager mqttClientSessionsManager) + public MqttClientSession(string clientId, MqttServerOptions options, MqttClientSessionsManager mqttClientSessionsManager, MqttNetTrace trace) { + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); ClientId = clientId; _options = options ?? throw new ArgumentNullException(nameof(options)); _mqttClientSessionsManager = mqttClientSessionsManager ?? throw new ArgumentNullException(nameof(mqttClientSessionsManager)); - _pendingMessagesQueue = new MqttClientPendingMessagesQueue(options, this); + _pendingMessagesQueue = new MqttClientPendingMessagesQueue(options, this, trace); } public string ClientId { get; } @@ -58,11 +60,11 @@ namespace MQTTnet.Core.Server } catch (MqttCommunicationException exception) { - MqttNetTrace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", ClientId); + _trace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", ClientId); } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); + _trace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); } } @@ -79,7 +81,7 @@ namespace MQTTnet.Core.Server _adapter = null; - MqttNetTrace.Information(nameof(MqttClientSession), "Client '{0}': Disconnected.", ClientId); + _trace.Information(nameof(MqttClientSession), "Client '{0}': Disconnected.", ClientId); } public void EnqueuePublishPacket(MqttPublishPacket publishPacket) @@ -92,7 +94,7 @@ namespace MQTTnet.Core.Server } _pendingMessagesQueue.Enqueue(publishPacket); - MqttNetTrace.Verbose(nameof(MqttClientSession), "Client '{0}': Enqueued pending publish packet.", ClientId); + _trace.Verbose(nameof(MqttClientSession), "Client '{0}': Enqueued pending publish packet.", ClientId); } public void Dispose() @@ -116,12 +118,12 @@ namespace MQTTnet.Core.Server } catch (MqttCommunicationException exception) { - MqttNetTrace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", ClientId); + _trace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", ClientId); Stop(); } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); + _trace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); Stop(); } } @@ -163,7 +165,7 @@ namespace MQTTnet.Core.Server } else { - MqttNetTrace.Warning(nameof(MqttClientSession), "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); + _trace.Warning(nameof(MqttClientSession), "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); Stop(); } } diff --git a/MQTTnet.Core/Server/MqttClientSessionsManager.cs b/MQTTnet.Core/Server/MqttClientSessionsManager.cs index 9c9fbfdc..3a93d089 100644 --- a/MQTTnet.Core/Server/MqttClientSessionsManager.cs +++ b/MQTTnet.Core/Server/MqttClientSessionsManager.cs @@ -17,11 +17,13 @@ namespace MQTTnet.Core.Server { private readonly Dictionary _clientSessions = new Dictionary(); private readonly MqttServerOptions _options; + private readonly MqttNetTrace _trace; - public MqttClientSessionsManager(MqttServerOptions options) + public MqttClientSessionsManager(MqttServerOptions options, MqttNetTrace trace) { + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); _options = options ?? throw new ArgumentNullException(nameof(options)); - RetainedMessagesManager = new MqttClientRetainedMessagesManager(options); + RetainedMessagesManager = new MqttClientRetainedMessagesManager(options, trace); } public event EventHandler ApplicationMessageReceived; @@ -74,7 +76,7 @@ namespace MQTTnet.Core.Server } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttServer), exception, exception.Message); + _trace.Error(nameof(MqttServer), exception, exception.Message); } finally { @@ -124,7 +126,7 @@ namespace MQTTnet.Core.Server } catch (Exception exception) { - MqttNetTrace.Error(nameof(MqttClientSessionsManager), exception, "Error while processing application message"); + _trace.Error(nameof(MqttClientSessionsManager), exception, "Error while processing application message"); } lock (_clientSessions) @@ -158,11 +160,11 @@ namespace MQTTnet.Core.Server _clientSessions.Remove(connectPacket.ClientId); clientSession.Dispose(); clientSession = null; - MqttNetTrace.Verbose(nameof(MqttClientSessionsManager), "Disposed existing session of client '{0}'.", connectPacket.ClientId); + _trace.Verbose(nameof(MqttClientSessionsManager), "Disposed existing session of client '{0}'.", connectPacket.ClientId); } else { - MqttNetTrace.Verbose(nameof(MqttClientSessionsManager), "Reusing existing session of client '{0}'.", connectPacket.ClientId); + _trace.Verbose(nameof(MqttClientSessionsManager), "Reusing existing session of client '{0}'.", connectPacket.ClientId); } } @@ -171,10 +173,10 @@ namespace MQTTnet.Core.Server { isExistingSession = false; - clientSession = new MqttClientSession(connectPacket.ClientId, _options, this); + clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _trace); _clientSessions[connectPacket.ClientId] = clientSession; - MqttNetTrace.Verbose(nameof(MqttClientSessionsManager), "Created a new session for client '{0}'.", connectPacket.ClientId); + _trace.Verbose(nameof(MqttClientSessionsManager), "Created a new session for client '{0}'.", connectPacket.ClientId); } return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession }; diff --git a/MQTTnet.Core/Server/MqttServer.cs b/MQTTnet.Core/Server/MqttServer.cs index d1615d81..7b13d719 100644 --- a/MQTTnet.Core/Server/MqttServer.cs +++ b/MQTTnet.Core/Server/MqttServer.cs @@ -10,18 +10,20 @@ namespace MQTTnet.Core.Server { public sealed class MqttServer : IMqttServer { + private readonly MqttNetTrace _trace; private readonly MqttClientSessionsManager _clientSessionsManager; private readonly ICollection _adapters; private readonly MqttServerOptions _options; private CancellationTokenSource _cancellationTokenSource; - public MqttServer(MqttServerOptions options, ICollection adapters) + public MqttServer(MqttServerOptions options, ICollection adapters, MqttNetTrace trace) { + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); _options = options ?? throw new ArgumentNullException(nameof(options)); _adapters = adapters ?? throw new ArgumentNullException(nameof(adapters)); - _clientSessionsManager = new MqttClientSessionsManager(options); + _clientSessionsManager = new MqttClientSessionsManager(options, trace); _clientSessionsManager.ApplicationMessageReceived += (s, e) => ApplicationMessageReceived?.Invoke(s, e); _clientSessionsManager.ClientConnected += OnClientConnected; _clientSessionsManager.ClientDisconnected += OnClientDisconnected; @@ -57,7 +59,7 @@ namespace MQTTnet.Core.Server await adapter.StartAsync(_options); } - MqttNetTrace.Information(nameof(MqttServer), "Started."); + _trace.Information(nameof(MqttServer), "Started."); } public async Task StopAsync() @@ -74,7 +76,7 @@ namespace MQTTnet.Core.Server _clientSessionsManager.Clear(); - MqttNetTrace.Information(nameof(MqttServer), "Stopped."); + _trace.Information(nameof(MqttServer), "Stopped."); } private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) @@ -84,13 +86,13 @@ namespace MQTTnet.Core.Server private void OnClientConnected(object sender, MqttClientConnectedEventArgs eventArgs) { - MqttNetTrace.Information(nameof(MqttServer), "Client '{0}': Connected.", eventArgs.Client.ClientId); + _trace.Information(nameof(MqttServer), "Client '{0}': Connected.", eventArgs.Client.ClientId); ClientConnected?.Invoke(this, eventArgs); } private void OnClientDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs) { - MqttNetTrace.Information(nameof(MqttServer), "Client '{0}': Disconnected.", eventArgs.Client.ClientId); + _trace.Information(nameof(MqttServer), "Client '{0}': Disconnected.", eventArgs.Client.ClientId); ClientDisconnected?.Invoke(this, eventArgs); } } diff --git a/MQTTnet.sln b/MQTTnet.sln index 4d49d12f..79e8b4d9 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}" ProjectSection(SolutionItems) = preProject + LICENSE = LICENSE README.md = README.md EndProjectSection EndProject diff --git a/README.md b/README.md index 2ded79f3..3d7be1e6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![BCH compliance](https://bettercodehub.com/edge/badge/chkr1011/MQTTnet?branch=master)](https://bettercodehub.com/) # MQTTnet -MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). The implementation is based on the documentation from http://mqtt.org/. +MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). The implementation is based on the documentation from http://mqtt.org/. # Features @@ -18,7 +18,7 @@ MQTTnet is a .NET library for MQTT based communication. It provides a MQTT clien * Performance optimized (processing ~27.000 messages / second)* * Interfaces included for mocking and testing * Access to internal trace messages -* Unit tested (62+ tests) +* Unit tested (67+ tests) \* Tested on local machine with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetFramework_. @@ -61,4 +61,26 @@ This library is used in the following projects: * MQTT Client Rx (Wrapper for Reactive Extensions, https://github.com/1iveowl/MQTTClient.rx) * Wirehome (Open Source Home Automation system for .NET, https://github.com/chkr1011/Wirehome) -If you use this library and want to see your project here please let me know. \ No newline at end of file +If you use this library and want to see your project here please let me know. + +# MIT License + +Copyright (c) 2017 Christian Kratky + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/MqttApplicationMessageBuilderTests.cs b/Tests/MQTTnet.Core.Tests/MqttApplicationMessageBuilderTests.cs new file mode 100644 index 00000000..c6a70174 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/MqttApplicationMessageBuilderTests.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Core.Protocol; + +namespace MQTTnet.Core.Tests +{ + [TestClass] + public class MqttApplicationMessageBuilderTests + { + [TestMethod] + public void CreateApplicationMessage_TopicOnly() + { + var message = new MqttApplicationMessageBuilder().WithTopic("Abc").Build(); + Assert.AreEqual("Abc", message.Topic); + Assert.IsFalse(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); + } + + [TestMethod] + public void CreateApplicationMessage_TimeStampPayload() + { + var message = new MqttApplicationMessageBuilder().WithTopic("xyz").WithPayload(TimeSpan.FromSeconds(360).ToString()).Build(); + Assert.AreEqual("xyz", message.Topic); + Assert.IsFalse(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); + Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00"); + } + + [TestMethod] + public void CreateApplicationMessage_StreamPayload() + { + var stream = new MemoryStream(Encoding.UTF8.GetBytes("xHello")) { Position = 1 }; + + var message = new MqttApplicationMessageBuilder().WithTopic("123").WithPayload(stream).Build(); + Assert.AreEqual("123", message.Topic); + Assert.IsFalse(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); + Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello"); + } + + [TestMethod] + public void CreateApplicationMessage_Retained() + { + var message = new MqttApplicationMessageBuilder().WithTopic("lol").WithRetainFlag().Build(); + Assert.AreEqual("lol", message.Topic); + Assert.IsTrue(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); + } + + [TestMethod] + public void CreateApplicationMessage_QosLevel2() + { + var message = new MqttApplicationMessageBuilder().WithTopic("rofl").WithRetainFlag().WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce).Build(); + Assert.AreEqual("rofl", message.Topic); + Assert.IsTrue(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.ExactlyOnce, message.QualityOfServiceLevel); + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/MqttApplicationMessageFactoryTests.cs b/Tests/MQTTnet.Core.Tests/MqttApplicationMessageFactoryTests.cs deleted file mode 100644 index f0f464a0..00000000 --- a/Tests/MQTTnet.Core.Tests/MqttApplicationMessageFactoryTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Text; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Core.Protocol; - -namespace MQTTnet.Core.Tests -{ - [TestClass] - public class MqttApplicationMessageFactoryTests - { - [TestMethod] - public void CreateApplicationMessage_TopicOnly() - { - var message = new MqttApplicationMessageFactory().CreateApplicationMessage("Abc", MqttQualityOfServiceLevel.AtLeastOnce); - Assert.AreEqual("Abc", message.Topic); - Assert.IsFalse(message.Retain); - Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, message.QualityOfServiceLevel); - } - - [TestMethod] - public void CreateApplicationMessage_TimeStampPayload() - { - var message = new MqttApplicationMessageFactory().CreateApplicationMessage("xyz", TimeSpan.FromSeconds(360)); - Assert.AreEqual("xyz", message.Topic); - Assert.IsFalse(message.Retain); - Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00"); - } - } -} diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index 0e786dfc..63ef0bb7 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Core.Adapter; using MQTTnet.Core.Client; +using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; using MQTTnet.Core.Server; @@ -49,7 +50,7 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_WillMessage() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }); + var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); await s.StartAsync(); var willMessage = new MqttApplicationMessage("My/last/will", new byte[0], MqttQualityOfServiceLevel.AtMostOnce, false); @@ -73,7 +74,7 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_Unsubscribe() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }); + var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -109,7 +110,7 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_Publish() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }); + var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -132,7 +133,7 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_NoRetainedMessage() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }); + var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -155,7 +156,7 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_RetainedMessage() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }); + var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -178,7 +179,7 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_ClearRetainedMessage() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }); + var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -204,7 +205,7 @@ namespace MQTTnet.Core.Tests var storage = new TestStorage(); var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions { Storage = storage }, new List { serverAdapter }); + var s = new MqttServer(new MqttServerOptions { Storage = storage }, new List { serverAdapter }, new MqttNetTrace()); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -213,7 +214,7 @@ namespace MQTTnet.Core.Tests await s.StopAsync(); - s = new MqttServer(new MqttServerOptions { Storage = storage }, new List { serverAdapter }); + s = new MqttServer(new MqttServerOptions { Storage = storage }, new List { serverAdapter }, new MqttNetTrace()); await s.StartAsync(); var c2 = await serverAdapter.ConnectTestClient(s, "c2"); @@ -235,7 +236,7 @@ namespace MQTTnet.Core.Tests public Task SaveRetainedMessagesAsync(IList messages) { _messages = messages; - return Task.FromResult(0); + return Task.CompletedTask; } public Task> LoadRetainedMessagesAsync() @@ -252,7 +253,7 @@ namespace MQTTnet.Core.Tests int expectedReceivedMessagesCount) { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }); + var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); diff --git a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs index 6cfa9bbb..32fc2cc7 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MQTTnet.Core.Adapter; using MQTTnet.Core.Server; using MQTTnet.Core.Client; +using MQTTnet.Core.Diagnostics; namespace MQTTnet.Core.Tests { @@ -17,7 +18,7 @@ namespace MQTTnet.Core.Tests adapterA.Partner = adapterB; adapterB.Partner = adapterA; - var client = new MqttClient(new MqttCommunicationAdapterFactory(adapterA)); + var client = new MqttClient(new MqttCommunicationAdapterFactory(adapterA), new MqttNetTrace()); var connected = WaitForClientToConnect(server, clientId); FireClientAcceptedEvent(adapterB); diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 88130cb8..dab82353 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -45,10 +45,10 @@ namespace MQTTnet.TestApp.NetCore { MqttNetTrace.TraceMessagePublished += (s, e) => { - Console.WriteLine($">> [{DateTime.Now:O}] [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); - if (e.Exception != null) + Console.WriteLine($">> [{e.TraceMessage.Timestamp:O}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}"); + if (e.TraceMessage.Exception != null) { - Console.WriteLine(e.Exception); + Console.WriteLine(e.TraceMessage.Exception); } }; @@ -134,10 +134,10 @@ namespace MQTTnet.TestApp.NetCore { MqttNetTrace.TraceMessagePublished += (s, e) => { - Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); - if (e.Exception != null) + Console.WriteLine($">> [{e.TraceMessage.Timestamp:O}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}"); + if (e.TraceMessage.Exception != null) { - Console.WriteLine(e.Exception); + Console.WriteLine(e.TraceMessage.Exception); } }; @@ -162,11 +162,11 @@ namespace MQTTnet.TestApp.NetCore options.Storage = new RetainedMessageHandler(); - var certificate = new X509Certificate(@"C:\certs\test\test.cer", ""); - options.TlsEndpointOptions.Certificate = certificate.Export(X509ContentType.Cert); - options.ConnectionBacklog = 5; - options.DefaultEndpointOptions.IsEnabled = true; - options.TlsEndpointOptions.IsEnabled = false; + //var certificate = new X509Certificate(@"C:\certs\test\test.cer", ""); + //options.TlsEndpointOptions.Certificate = certificate.Export(X509ContentType.Cert); + //options.ConnectionBacklog = 5; + //options.DefaultEndpointOptions.IsEnabled = true; + //options.TlsEndpointOptions.IsEnabled = false; var mqttServer = new MqttServerFactory().CreateMqttServer(options); mqttServer.ClientDisconnected += (s, e) => @@ -189,6 +189,51 @@ namespace MQTTnet.TestApp.NetCore Console.ReadLine(); return Task.FromResult(0); } + + // ReSharper disable once UnusedMember.Local + private static async void WikiCode() + { + { + var client = new MqttClientFactory().CreateMqttClient(new CustomTraceHandler("Client 1")); + + var message = new MqttApplicationMessageBuilder() + .WithTopic("MyTopic") + .WithPayload("Hello World") + .WithExactlyOnceQoS() + .WithRetainFlag() + .Build(); + + await client.PublishAsync(message); + } + + { + var message = new MqttApplicationMessageBuilder() + .WithTopic("/MQTTnet/is/awesome") + .Build(); + } + } + } + + public class CustomTraceHandler : IMqttNetTraceHandler + { + private readonly string _clientId; + + public CustomTraceHandler(string clientId) + { + _clientId = clientId; + } + + public bool IsEnabled { get; } = true; + + public void HandleTraceMessage(MqttNetTraceMessage traceMessage) + { + // Client ID is added to the trace message. + Console.WriteLine($">> [{_clientId}] [{traceMessage.Timestamp:O}] [{traceMessage.ThreadId}] [{traceMessage.Source}] [{traceMessage.Level}]: {traceMessage.Message}"); + if (traceMessage.Exception != null) + { + Console.WriteLine(traceMessage.Exception); + } + } } public class RetainedMessageHandler : IMqttServerStorage @@ -213,7 +258,7 @@ namespace MQTTnet.TestApp.NetCore { retainedMessages = new List(); } - + return Task.FromResult(retainedMessages); } } diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index de795d55..8826e135 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -29,10 +29,10 @@ namespace MQTTnet.TestApp.UniversalWindows { await Trace.Dispatcher.RunAsync(CoreDispatcherPriority.High, () => { - var text = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] [{e.Level}] [{e.Source}] [{e.ThreadId}] [{e.Message}]{Environment.NewLine}"; - if (e.Exception != null) + var text = $"[{e.TraceMessage.Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{e.TraceMessage.Level}] [{e.TraceMessage.Source}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Message}]{Environment.NewLine}"; + if (e.TraceMessage.Exception != null) { - text += $"{e.Exception}{Environment.NewLine}"; + text += $"{e.TraceMessage.Exception}{Environment.NewLine}"; } Trace.Text += text; From f9eb6ed85ed99942aab8a1432094ceee7dd7b39b Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 21 Oct 2017 00:14:09 +0200 Subject: [PATCH 15/34] Add support for WebSocket server via ASP.NET Core 2.0 --- .../Implementations/WebSocketStream.cs | 6 +- .../Implementations/WebSocketStream.cs | 6 +- ...qttServerAdapterClientAcceptedEventArgs.cs | 3 + MQTTnet.Core/Diagnostics/MqttNetTrace.cs | 4 +- MQTTnet.Core/Server/MqttServer.cs | 2 +- MQTTnet.sln | 19 ++++ .../MQTTnet.TestApp.AspNetCore2.csproj | 20 ++++ .../MqttWebSocketServerAdapter.cs | 95 +++++++++++++++++++ Tests/MQTTnet.TestApp.AspNetCore2/Program.cs | 18 ++++ Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs | 55 +++++++++++ 10 files changed, 219 insertions(+), 9 deletions(-) create mode 100644 Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj create mode 100644 Tests/MQTTnet.TestApp.AspNetCore2/MqttWebSocketServerAdapter.cs create mode 100644 Tests/MQTTnet.TestApp.AspNetCore2/Program.cs create mode 100644 Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs index 6efe7506..d38de6f1 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs @@ -8,11 +8,11 @@ namespace MQTTnet.Implementations { public class WebSocketStream : Stream { - private readonly ClientWebSocket _webSocket; + private readonly WebSocket _webSocket; - public WebSocketStream(ClientWebSocket webSocket) + public WebSocketStream(WebSocket webSocket) { - _webSocket = webSocket; + _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); } public override bool CanRead => true; diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs index 69225b9f..1843c6a6 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs @@ -8,11 +8,11 @@ namespace MQTTnet.Implementations { public class WebSocketStream : Stream { - private readonly ClientWebSocket _webSocket; + private readonly WebSocket _webSocket; - public WebSocketStream(ClientWebSocket webSocket) + public WebSocketStream(WebSocket webSocket) { - _webSocket = webSocket; + _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); } public override void Flush() diff --git a/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs b/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs index 6d7fb54b..6caca0e9 100644 --- a/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs +++ b/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace MQTTnet.Core.Adapter { @@ -10,5 +11,7 @@ namespace MQTTnet.Core.Adapter } public IMqttCommunicationAdapter Client { get; } + + public Task SessionTask { get; set; } } } diff --git a/MQTTnet.Core/Diagnostics/MqttNetTrace.cs b/MQTTnet.Core/Diagnostics/MqttNetTrace.cs index 35a828cb..ece6cf75 100644 --- a/MQTTnet.Core/Diagnostics/MqttNetTrace.cs +++ b/MQTTnet.Core/Diagnostics/MqttNetTrace.cs @@ -6,9 +6,9 @@ namespace MQTTnet.Core.Diagnostics { private readonly IMqttNetTraceHandler _traceHandler; - public MqttNetTrace(IMqttNetTraceHandler traceHandler = null) + public MqttNetTrace(IMqttNetTraceHandler customTraceHandler = null) { - _traceHandler = traceHandler ?? this; + _traceHandler = customTraceHandler ?? this; } public static event EventHandler TraceMessagePublished; diff --git a/MQTTnet.Core/Server/MqttServer.cs b/MQTTnet.Core/Server/MqttServer.cs index 7b13d719..7da59575 100644 --- a/MQTTnet.Core/Server/MqttServer.cs +++ b/MQTTnet.Core/Server/MqttServer.cs @@ -81,7 +81,7 @@ namespace MQTTnet.Core.Server private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) { - Task.Run(() =>_clientSessionsManager.RunClientSessionAsync(eventArgs.Client), _cancellationTokenSource.Token); + eventArgs.SessionTask = Task.Run(async () => await _clientSessionsManager.RunClientSessionAsync(eventArgs.Client), _cancellationTokenSource.Token); } private void OnClientConnected(object sender, MqttClientConnectedEventArgs eventArgs) diff --git a/MQTTnet.sln b/MQTTnet.sln index 79e8b4d9..db44de84 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -31,6 +31,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.NetCore", "Tests\MQTTnet.TestApp.NetCore\MQTTnet.TestApp.NetCore.csproj", "{3D283AAD-AAA8-4339-8394-52F80B6304DB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp.AspNetCore2", "Tests\MQTTnet.TestApp.AspNetCore2\MQTTnet.TestApp.AspNetCore2.csproj", "{C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -145,6 +147,22 @@ Global {3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x64.Build.0 = Release|Any CPU {3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x86.ActiveCfg = Release|Any CPU {3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x86.Build.0 = Release|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|ARM.ActiveCfg = Debug|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|ARM.Build.0 = Debug|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|x64.ActiveCfg = Debug|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|x64.Build.0 = Debug|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|x86.Build.0 = Debug|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|Any CPU.Build.0 = Release|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|ARM.ActiveCfg = Release|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|ARM.Build.0 = Release|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|x64.ActiveCfg = Release|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|x64.Build.0 = Release|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|x86.ActiveCfg = Release|Any CPU + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -155,6 +173,7 @@ Global {FF1F72D6-9524-4422-9497-3CC0002216ED} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {3587E506-55A2-4EB3-99C7-DC01E42D25D2} = {32A630A7-2598-41D7-B625-204CD906F5FB} {3D283AAD-AAA8-4339-8394-52F80B6304DB} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} + {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj new file mode 100644 index 00000000..ce35f430 --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.0 + + + + + + + + + + + + + + + + diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MqttWebSocketServerAdapter.cs b/Tests/MQTTnet.TestApp.AspNetCore2/MqttWebSocketServerAdapter.cs new file mode 100644 index 00000000..c9f52933 --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MqttWebSocketServerAdapter.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet.Core.Adapter; +using MQTTnet.Core.Channel; +using MQTTnet.Core.Diagnostics; +using MQTTnet.Core.Serializer; +using MQTTnet.Core.Server; +using MQTTnet.Implementations; + +namespace MQTTnet.TestApp.AspNetCore2 +{ + public class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable + { + private readonly MqttNetTrace _trace; + + public MqttWebSocketServerAdapter(MqttNetTrace trace) + { + _trace = trace ?? throw new ArgumentNullException(nameof(trace)); + } + + public event EventHandler ClientAccepted; + + public Task StartAsync(MqttServerOptions options) + { + return Task.CompletedTask; + } + + public Task StopAsync() + { + return Task.FromResult(0); + } + + public Task AcceptWebSocketAsync(WebSocket webSocket) + { + if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); + + var channel = new MqttWebSocketServerChannel(webSocket); + var clientAdapter = new MqttChannelCommunicationAdapter(channel, new MqttPacketSerializer(), _trace); + + var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); + ClientAccepted?.Invoke(this, eventArgs); + return eventArgs.SessionTask; + } + + public void Dispose() + { + StopAsync(); + } + + private class MqttWebSocketServerChannel : IMqttCommunicationChannel, IDisposable + { + private readonly WebSocket _webSocket; + + public MqttWebSocketServerChannel(WebSocket webSocket) + { + _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); + + RawReceiveStream = new WebSocketStream(_webSocket); + } + + public Stream SendStream => RawReceiveStream; + public Stream ReceiveStream => RawReceiveStream; + public Stream RawReceiveStream { get; } + + public Task ConnectAsync() + { + return Task.CompletedTask; + } + + public Task DisconnectAsync() + { + RawReceiveStream?.Dispose(); + + if (_webSocket == null) + { + return Task.CompletedTask; + } + + return _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); + } + + public void Dispose() + { + RawReceiveStream?.Dispose(); + SendStream?.Dispose(); + ReceiveStream?.Dispose(); + + _webSocket?.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs new file mode 100644 index 00000000..38b8c12d --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace MQTTnet.TestApp.AspNetCore2 +{ + public static class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + private static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .Build(); + } +} diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs new file mode 100644 index 00000000..d730d2cd --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MQTTnet.Core.Adapter; +using MQTTnet.Core.Diagnostics; +using MQTTnet.Core.Server; + +namespace MQTTnet.TestApp.AspNetCore2 +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + } + + public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(LogLevel.Debug); + + MqttNetTrace.TraceMessagePublished += (s, e) => + { + Debug.WriteLine($">> [{e.TraceMessage.Timestamp}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}"); + if (e.TraceMessage.Exception != null) + { + Debug.WriteLine(e.TraceMessage.Exception.Message); + } + }; + + var trace = new MqttNetTrace(); + var adapter = new MqttWebSocketServerAdapter(trace); + var options = new MqttServerOptions(); + var mqttServer = new MqttServer(options, new List { adapter }, new MqttNetTrace()); + await mqttServer.StartAsync(); + + app.UseWebSockets(); + app.Use(async (context, next) => + { + if (context.WebSockets.IsWebSocketRequest) + { + using (var webSocket = await context.WebSockets.AcceptWebSocketAsync()) + { + await adapter.AcceptWebSocketAsync(webSocket); + } + } + else + { + await next(); + } + }); + } + } +} From d052f409b667c9ca4ebc9c83756543332a601af9 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 21 Oct 2017 00:16:53 +0200 Subject: [PATCH 16/34] Update documentation --- Build/MQTTnet.nuspec | 3 ++- README.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 128f040e..d82e3692 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -9,11 +9,12 @@ https://github.com/chkr1011/MQTTnet https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png false - MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). + MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). * [Core] Merged the .NET Framwork and netstandard projects (Thanks to @JanEggers) * [Core] Migrated the trace to a non-static approach (Breaking Change!) * [Core] Added a builder for application messages using a fluent API * [Client] Added a first version of a managed client which will manage the connection, subscription etc. automatically (Thanks to @JTrotta) +* [Server] Added support for WebSockets via ASP.NET Core 2.0 (Thanks to @ChristianRiedl) Copyright Christian Kratky 2016-2017 MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M diff --git a/README.md b/README.md index 3d7be1e6..109c98cf 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,10 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * List of connected clients available * Supports connected clients with different protocol versions at the same time * Able to publish its own messages (no loopback client required) -* Able to receive every messages (no loopback client required) +* Able to receive every message (no loopback client required) * Extensible client credential validation * Retained messages are supported including persisting via interface methods (own implementation required) +* WebSockets supported (via ASP.NET Core 2.0) # Supported frameworks * .NET Standard 1.3+ From 24c35de33035f82a0d78df0bf58d30a696210015 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 21 Oct 2017 15:49:50 +0200 Subject: [PATCH 17/34] Refactor manage client --- Build/MQTTnet.nuspec | 1 + MQTTnet.Core/Client/IMqttClient.cs | 4 +- MQTTnet.Core/Client/MqttClient.cs | 13 +- .../Client/MqttClientConnectResult.cs | 12 + .../Client/MqttClientConnectedEventArgs.cs | 14 + .../IManagedMqttClientOptions.cs | 1 - .../ManagedClient/ManagedMqttClient.cs | 333 +++++++++++++----- .../ManagedMqttClientExtensions.cs | 30 ++ .../ManagedClient/ManagedMqttClientOptions.cs | 1 - .../ManagedMqttClientStorageManager.cs | 75 ++++ .../ManagedClient/ReconnectionResult.cs | 9 + MQTTnet.Core/Packets/TopicFilter.cs | 10 + MQTTnet.Core/Server/IMqttServer.cs | 3 +- MQTTnet.sln | 4 +- .../TestMqttServerAdapter.cs | 2 +- .../ManagedClientTest.cs | 62 ++++ Tests/MQTTnet.TestApp.NetCore/Program.cs | 19 +- 17 files changed, 485 insertions(+), 108 deletions(-) create mode 100644 MQTTnet.Core/Client/MqttClientConnectResult.cs create mode 100644 MQTTnet.Core/Client/MqttClientConnectedEventArgs.cs create mode 100644 MQTTnet.Core/ManagedClient/ManagedMqttClientExtensions.cs create mode 100644 MQTTnet.Core/ManagedClient/ManagedMqttClientStorageManager.cs create mode 100644 MQTTnet.Core/ManagedClient/ReconnectionResult.cs create mode 100644 Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index d82e3692..b78712df 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -15,6 +15,7 @@ * [Core] Added a builder for application messages using a fluent API * [Client] Added a first version of a managed client which will manage the connection, subscription etc. automatically (Thanks to @JTrotta) * [Server] Added support for WebSockets via ASP.NET Core 2.0 (Thanks to @ChristianRiedl) +* [Client] The session state response from the server is now returned in the _ConnectAsync_ method and also part of the _Connected_ event args Copyright Christian Kratky 2016-2017 MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M diff --git a/MQTTnet.Core/Client/IMqttClient.cs b/MQTTnet.Core/Client/IMqttClient.cs index 7219c58d..04b5dcda 100644 --- a/MQTTnet.Core/Client/IMqttClient.cs +++ b/MQTTnet.Core/Client/IMqttClient.cs @@ -10,10 +10,10 @@ namespace MQTTnet.Core.Client bool IsConnected { get; } event EventHandler ApplicationMessageReceived; - event EventHandler Connected; + event EventHandler Connected; event EventHandler Disconnected; - Task ConnectAsync(IMqttClientOptions options); + Task ConnectAsync(IMqttClientOptions options); Task DisconnectAsync(); Task> SubscribeAsync(IEnumerable topicFilters); diff --git a/MQTTnet.Core/Client/MqttClient.cs b/MQTTnet.Core/Client/MqttClient.cs index 4e595d7f..fe1e4aba 100644 --- a/MQTTnet.Core/Client/MqttClient.cs +++ b/MQTTnet.Core/Client/MqttClient.cs @@ -33,13 +33,13 @@ namespace MQTTnet.Core.Client _packetDispatcher = new MqttPacketDispatcher(trace); } - public event EventHandler Connected; + public event EventHandler Connected; public event EventHandler Disconnected; public event EventHandler ApplicationMessageReceived; public bool IsConnected { get; private set; } - public async Task ConnectAsync(IMqttClientOptions options) + public async Task ConnectAsync(IMqttClientOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -59,7 +59,7 @@ namespace MQTTnet.Core.Client _trace.Verbose(nameof(MqttClient), "Connection with server established."); await SetupIncomingPacketProcessingAsync(); - await AuthenticateAsync(options.WillMessage); + var connectResponse = await AuthenticateAsync(options.WillMessage); _trace.Verbose(nameof(MqttClient), "MQTT connection with server established."); @@ -69,7 +69,8 @@ namespace MQTTnet.Core.Client } IsConnected = true; - Connected?.Invoke(this, EventArgs.Empty); + Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent)); + return new MqttClientConnectResult(connectResponse.IsSessionPresent); } catch (Exception) { @@ -178,7 +179,7 @@ namespace MQTTnet.Core.Client } } - private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage) + private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage) { var connectPacket = new MqttConnectPacket { @@ -195,6 +196,8 @@ namespace MQTTnet.Core.Client { throw new MqttConnectingFailedException(response.ConnectReturnCode); } + + return response; } private async Task SetupIncomingPacketProcessingAsync() diff --git a/MQTTnet.Core/Client/MqttClientConnectResult.cs b/MQTTnet.Core/Client/MqttClientConnectResult.cs new file mode 100644 index 00000000..57db57df --- /dev/null +++ b/MQTTnet.Core/Client/MqttClientConnectResult.cs @@ -0,0 +1,12 @@ +namespace MQTTnet.Core.Client +{ + public class MqttClientConnectResult + { + public MqttClientConnectResult(bool isSessionPresent) + { + IsSessionPresent = isSessionPresent; + } + + public bool IsSessionPresent { get; } + } +} diff --git a/MQTTnet.Core/Client/MqttClientConnectedEventArgs.cs b/MQTTnet.Core/Client/MqttClientConnectedEventArgs.cs new file mode 100644 index 00000000..107a2c95 --- /dev/null +++ b/MQTTnet.Core/Client/MqttClientConnectedEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace MQTTnet.Core.Client +{ + public class MqttClientConnectedEventArgs : EventArgs + { + public MqttClientConnectedEventArgs(bool isSessionPresent) + { + IsSessionPresent = isSessionPresent; + } + + public bool IsSessionPresent { get; } + } +} diff --git a/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs b/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs index 33c91e3e..298de9a1 100644 --- a/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs +++ b/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs @@ -7,7 +7,6 @@ namespace MQTTnet.Core.ManagedClient { IMqttClientOptions ClientOptions { get; } - bool UseAutoReconnect { get; } TimeSpan AutoReconnectDelay { get; } IManagedMqttClientStorage Storage { get; } diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs index 989d1176..1a23912e 100644 --- a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs +++ b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -7,17 +8,24 @@ using MQTTnet.Core.Client; using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Exceptions; using MQTTnet.Core.Packets; +using MQTTnet.Core.Protocol; namespace MQTTnet.Core.ManagedClient { public class ManagedMqttClient { - private readonly List _messageQueue = new List(); - private readonly AutoResetEvent _messageQueueGate = new AutoResetEvent(false); - private readonly MqttClient _mqttClient; + private readonly ManagedMqttClientStorageManager _storageManager = new ManagedMqttClientStorageManager(); + private readonly BlockingCollection _messageQueue = new BlockingCollection(); + private readonly HashSet _subscriptions = new HashSet(); + + private readonly IMqttClient _mqttClient; private readonly MqttNetTrace _trace; + private CancellationTokenSource _connectionCancellationToken; + private CancellationTokenSource _publishingCancellationToken; + private IManagedMqttClientOptions _options; + private bool _subscriptionsNotPushed; public ManagedMqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory, MqttNetTrace trace) { @@ -30,120 +38,269 @@ namespace MQTTnet.Core.ManagedClient _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; } - public event EventHandler Connected; + public event EventHandler Connected; public event EventHandler Disconnected; public event EventHandler ApplicationMessageReceived; public bool IsConnected => _mqttClient.IsConnected; - - public void Start(IManagedMqttClientOptions options) + public async Task StartAsync(IManagedMqttClientOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); if (options.ClientOptions == null) throw new ArgumentException("The client options are not set.", nameof(options)); + if (!options.ClientOptions.CleanSession) + { + throw new NotSupportedException("The managed client does not support existing sessions."); + } - } + if (_connectionCancellationToken != null) + { + throw new InvalidOperationException("The managed client is already started."); + } - public void Stop() - { + _options = options; + await _storageManager.SetStorageAsync(_options.Storage).ConfigureAwait(false); + + if (_options.Storage != null) + { + var loadedMessages = await _options.Storage.LoadQueuedMessagesAsync().ConfigureAwait(false); + foreach (var loadedMessage in loadedMessages) + { + _messageQueue.Add(loadedMessage); + } + } + + _connectionCancellationToken = new CancellationTokenSource(); +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Task.Factory.StartNew(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + + _trace.Information(nameof(ManagedMqttClient), "Started"); } - public async Task ConnectAsync(IManagedMqttClientOptions options) + public Task StopAsync() { - //////TODO VERY BAD - ////_options = options as ManagedMqttClientTcpOptions; - ////this._usePersistance = _options.Storage != null; - ////await _mqttClient.ConnectAsync(options); - ////SetupOutgoingPacketProcessingAsync(); + _connectionCancellationToken?.Cancel(false); + _connectionCancellationToken = null; - //////load persistentMessages - ////////if (_usePersistance) - ////////{ - //////// if (_persistentMessagesManager == null) - //////// _persistentMessagesManager = new ManagedMqttClientMessagesManager(_options); - //////// await _persistentMessagesManager.LoadMessagesAsync(); - //////// await InternalPublishAsync(_persistentMessagesManager.GetMessages(), false); - ////////} + while (_messageQueue.Any()) + { + _messageQueue.Take(); + } + + return Task.FromResult(0); } - public async Task DisconnectAsync() - { - await _mqttClient.DisconnectAsync(); - } - - public async Task UnsubscribeAsync(IEnumerable topicFilters) - { - // TODO: Move all subscriptions to list an subscribe after connection has lost. But only if server session is new. - await _mqttClient.UnsubscribeAsync(topicFilters); - } - - public void Enqueue(IEnumerable applicationMessages) + public Task EnqueueAsync(IEnumerable applicationMessages) { if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); - ThrowIfNotConnected(); + foreach (var applicationMessage in applicationMessages) + { + _messageQueue.Add(applicationMessage); + } - _messageQueue.AddRange(applicationMessages); - _options.Storage?.SaveQueuedMessagesAsync(_messageQueue.ToList()); - - _messageQueueGate.Set(); - } - - public async Task> SubscribeAsync(IEnumerable topicFilters) - { - return await _mqttClient.SubscribeAsync(topicFilters); + return Task.FromResult(0); } - private void ThrowIfNotConnected() + public Task SubscribeAsync(IEnumerable topicFilters) { - if (!IsConnected) throw new MqttCommunicationException("The client is not connected."); - } + if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - private void SetupOutgoingPacketProcessingAsync() - { - //Task.Factory.StartNew( - // () => SendPackets(_mqttClient._cancellationTokenSource.Token), - // _mqttClient._cancellationTokenSource.Token, - // TaskCreationOptions.LongRunning, - // TaskScheduler.Default).ConfigureAwait(false); + lock (_subscriptions) + { + foreach (var topicFilter in topicFilters) + { + if (_subscriptions.Add(topicFilter)) + { + _subscriptionsNotPushed = true; + } + } + } + + return Task.FromResult(0); } - private async Task SendPackets(CancellationToken cancellationToken) + public Task UnsubscribeAsync(IEnumerable topicFilters) { - //MqttNetTrace.Information(nameof(MqttClientManaged), "Start sending packets."); - //MqttApplicationMessage messageInQueue = null; + lock (_subscriptions) + { + foreach (var topicFilter in topicFilters) + { + if (_subscriptions.Remove(topicFilter)) + { + _subscriptionsNotPushed = true; + } + } + } - //try - //{ - // while (!cancellationToken.IsCancellationRequested) - // { - // messageInQueue = _inflightQueue.Take(); - // await _mqttClient.PublishAsync(new List() { messageInQueue }); - // if (_usePersistance) - // await _persistentMessagesManager.Remove(messageInQueue); - // } - //} - //catch (OperationCanceledException) - //{ - //} - //catch (MqttCommunicationException exception) - //{ - // MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending packets."); - // //message not send, equeue it again - // if (messageInQueue != null) - // _inflightQueue.Add(messageInQueue); - //} - //catch (Exception exception) - //{ - // MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while sending packets."); - // await DisconnectAsync().ConfigureAwait(false); - //} - //finally - //{ - // MqttNetTrace.Information(nameof(MqttClient), "Stopped sending packets."); - //} + return Task.FromResult(0); + } + + private async Task MaintainConnectionAsync(CancellationToken cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + var connectionState = await ReconnectIfRequiredAsync().ConfigureAwait(false); + if (connectionState == ReconnectionResult.NotConnected) + { + _publishingCancellationToken?.Cancel(false); + _publishingCancellationToken = null; + + await Task.Delay(_options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false); + continue; + } + + if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed) + { + await PushSubscriptionsAsync(); + + _publishingCancellationToken = new CancellationTokenSource(); + +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Task.Factory.StartNew(() => PublishQueuedMessagesAsync(_publishingCancellationToken.Token), _publishingCancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + + continue; + } + + if (connectionState == ReconnectionResult.StillConnected) + { + await Task.Delay(100, _connectionCancellationToken.Token).ConfigureAwait(false); // Consider using the _Disconnected_ event here. (TaskCompletionSource) + } + } + } + catch (OperationCanceledException) + { + } + catch (MqttCommunicationException exception) + { + _trace.Warning(nameof(ManagedMqttClient), exception, "Communication exception while maintaining connection."); + } + catch (Exception exception) + { + _trace.Error(nameof(ManagedMqttClient), exception, "Unhandled exception while maintaining connection."); + } + finally + { + await _mqttClient.DisconnectAsync().ConfigureAwait(false); + _trace.Information(nameof(ManagedMqttClient), "Stopped"); + } + } + + private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + var message = _messageQueue.Take(cancellationToken); + if (message == null) + { + continue; + } + + if (cancellationToken.IsCancellationRequested) + { + continue; + } + + await TryPublishQueuedMessageAsync(message).ConfigureAwait(false); + } + } + catch (OperationCanceledException) + { + } + finally + { + _trace.Information(nameof(ManagedMqttClient), "Stopped publishing messages"); + } + } + + private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message) + { + try + { + await _mqttClient.PublishAsync(message).ConfigureAwait(false); + } + catch (MqttCommunicationException exception) + { + _trace.Warning(nameof(ManagedMqttClient), exception, "Publishing application message failed."); + + if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) + { + _messageQueue.Add(message); + } + } + catch (Exception exception) + { + _trace.Error(nameof(ManagedMqttClient), exception, "Unhandled exception while publishing queued application message."); + } + } + + private async Task SaveAsync(List messages) + { + if (messages == null) + { + return; + } + + var storage = _options.Storage; + if (storage != null) + { + return; + } + + await _options.Storage.SaveQueuedMessagesAsync(messages); + } + + private async Task PushSubscriptionsAsync() + { + _trace.Information(nameof(ManagedMqttClient), "Synchronizing subscriptions"); + + List subscriptions; + lock (_subscriptions) + { + subscriptions = _subscriptions.ToList(); + _subscriptionsNotPushed = false; + } + + if (!_subscriptions.Any()) + { + return; + } + + try + { + await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false); + } + catch (Exception exception) + { + _trace.Warning(nameof(ManagedMqttClient), exception, "Synchronizing subscriptions failed"); + _subscriptionsNotPushed = true; + } + } + + private async Task ReconnectIfRequiredAsync() + { + if (_mqttClient.IsConnected) + { + return ReconnectionResult.StillConnected; + } + + try + { + await _mqttClient.ConnectAsync(_options.ClientOptions).ConfigureAwait(false); + return ReconnectionResult.Reconnected; + } + catch (Exception) + { + return ReconnectionResult.NotConnected; + } } private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs) @@ -156,7 +313,7 @@ namespace MQTTnet.Core.ManagedClient Disconnected?.Invoke(this, eventArgs); } - private void OnConnected(object sender, EventArgs eventArgs) + private void OnConnected(object sender, MqttClientConnectedEventArgs eventArgs) { Connected?.Invoke(this, eventArgs); } diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClientExtensions.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClientExtensions.cs new file mode 100644 index 00000000..62aa166f --- /dev/null +++ b/MQTTnet.Core/ManagedClient/ManagedMqttClientExtensions.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; +using MQTTnet.Core.Packets; + +namespace MQTTnet.Core.ManagedClient +{ + public static class ManagedMqttClientExtensions + { + public static Task EnqueueAsync(this ManagedMqttClient managedClient, params MqttApplicationMessage[] applicationMessages) + { + if (managedClient == null) throw new ArgumentNullException(nameof(managedClient)); + + return managedClient.EnqueueAsync(applicationMessages); + } + + public static Task SubscribeAsync(this ManagedMqttClient managedClient, params TopicFilter[] topicFilters) + { + if (managedClient == null) throw new ArgumentNullException(nameof(managedClient)); + + return managedClient.SubscribeAsync(topicFilters); + } + + public static Task UnsubscribeAsync(this ManagedMqttClient managedClient, params TopicFilter[] topicFilters) + { + if (managedClient == null) throw new ArgumentNullException(nameof(managedClient)); + + return managedClient.UnsubscribeAsync(topicFilters); + } + } +} diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs index bcddb63a..2e24fd0a 100644 --- a/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs +++ b/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs @@ -7,7 +7,6 @@ namespace MQTTnet.Core.ManagedClient { public IMqttClientOptions ClientOptions { get; set; } - public bool UseAutoReconnect { get; set; } = true; public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5); public IManagedMqttClientStorage Storage { get; set; } diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClientStorageManager.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClientStorageManager.cs new file mode 100644 index 00000000..0810f36e --- /dev/null +++ b/MQTTnet.Core/ManagedClient/ManagedMqttClientStorageManager.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MQTTnet.Core.ManagedClient +{ + public class ManagedMqttClientStorageManager + { + private readonly List _applicationMessages = new List(); + private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); + private IManagedMqttClientStorage _storage; + + public async Task SetStorageAsync(IManagedMqttClientStorage storage) + { + await _semaphore.WaitAsync().ConfigureAwait(false); + try + { + _storage = storage; + } + finally + { + _semaphore.Release(); + } + } + + public async Task AddAsync(MqttApplicationMessage applicationMessage) + { + await _semaphore.WaitAsync().ConfigureAwait(false); + try + { + if (_storage == null) + { + return; + } + + _applicationMessages.Add(applicationMessage); + await SaveAsync().ConfigureAwait(false); + } + finally + { + _semaphore.Release(); + } + } + + public async Task RemoveAsync(MqttApplicationMessage applicationMessage) + { + await _semaphore.WaitAsync().ConfigureAwait(false); + try + { + if (_storage == null) + { + return; + } + + var index = _applicationMessages.IndexOf(applicationMessage); + if (index == -1) + { + return; + } + + _applicationMessages.RemoveAt(index); + await SaveAsync().ConfigureAwait(false); + } + finally + { + _semaphore.Release(); + } + } + + private Task SaveAsync() + { + return _storage.SaveQueuedMessagesAsync(_applicationMessages); + } + } +} diff --git a/MQTTnet.Core/ManagedClient/ReconnectionResult.cs b/MQTTnet.Core/ManagedClient/ReconnectionResult.cs new file mode 100644 index 00000000..fae07706 --- /dev/null +++ b/MQTTnet.Core/ManagedClient/ReconnectionResult.cs @@ -0,0 +1,9 @@ +namespace MQTTnet.Core.ManagedClient +{ + public enum ReconnectionResult + { + StillConnected, + Reconnected, + NotConnected + } +} diff --git a/MQTTnet.Core/Packets/TopicFilter.cs b/MQTTnet.Core/Packets/TopicFilter.cs index e2ae4223..6786b1d7 100644 --- a/MQTTnet.Core/Packets/TopicFilter.cs +++ b/MQTTnet.Core/Packets/TopicFilter.cs @@ -13,5 +13,15 @@ namespace MQTTnet.Core.Packets public string Topic { get; } public MqttQualityOfServiceLevel QualityOfServiceLevel { get; } + + public override int GetHashCode() + { + return QualityOfServiceLevel.GetHashCode() ^ (Topic ?? string.Empty).GetHashCode(); + } + + public override string ToString() + { + return Topic + "@" + QualityOfServiceLevel; + } } } \ No newline at end of file diff --git a/MQTTnet.Core/Server/IMqttServer.cs b/MQTTnet.Core/Server/IMqttServer.cs index a35a2661..db3e1b0d 100644 --- a/MQTTnet.Core/Server/IMqttServer.cs +++ b/MQTTnet.Core/Server/IMqttServer.cs @@ -6,10 +6,11 @@ namespace MQTTnet.Core.Server { public interface IMqttServer { - event EventHandler ApplicationMessageReceived; event EventHandler ClientConnected; event EventHandler ClientDisconnected; + event EventHandler ApplicationMessageReceived; + IList GetConnectedClients(); void Publish(MqttApplicationMessage applicationMessage); diff --git a/MQTTnet.sln b/MQTTnet.sln index db44de84..56a56aa5 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27004.2002 +VisualStudioVersion = 15.0.27004.2005 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}" EndProject @@ -31,7 +31,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.NetCore", "Tests\MQTTnet.TestApp.NetCore\MQTTnet.TestApp.NetCore.csproj", "{3D283AAD-AAA8-4339-8394-52F80B6304DB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp.AspNetCore2", "Tests\MQTTnet.TestApp.AspNetCore2\MQTTnet.TestApp.AspNetCore2.csproj", "{C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.AspNetCore2", "Tests\MQTTnet.TestApp.AspNetCore2\MQTTnet.TestApp.AspNetCore2.csproj", "{C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs index 32fc2cc7..ae15cc25 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs @@ -32,7 +32,7 @@ namespace MQTTnet.Core.Tests { var tcs = new TaskCompletionSource(); - void Handler(object sender, MqttClientConnectedEventArgs args) + void Handler(object sender, Server.MqttClientConnectedEventArgs args) { if (args.Client.ClientId == clientId) { diff --git a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs new file mode 100644 index 00000000..4959f370 --- /dev/null +++ b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using MQTTnet.Core; +using MQTTnet.Core.Client; +using MQTTnet.Core.Diagnostics; +using MQTTnet.Core.ManagedClient; +using MQTTnet.Core.Packets; +using MQTTnet.Core.Protocol; + +namespace MQTTnet.TestApp.NetCore +{ + public static class ManagedClientTest + { + public static async Task RunAsync() + { + MqttNetTrace.TraceMessagePublished += (s, e) => + { + Console.WriteLine($">> [{e.TraceMessage.Timestamp:O}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}"); + if (e.TraceMessage.Exception != null) + { + Console.WriteLine(e.TraceMessage.Exception); + } + }; + + var options = new ManagedMqttClientOptions + { + ClientOptions = new MqttClientTcpOptions + { + Server = "broker.hivemq.com", + ClientId = "MQTTnetManagedClientTest" + }, + + AutoReconnectDelay = TimeSpan.FromSeconds(1) + }; + + try + { + var managedClient = new MqttClientFactory().CreateManagedMqttClient(); + managedClient.ApplicationMessageReceived += (s, e) => + { + Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic); + }; + + await managedClient.EnqueueAsync(new MqttApplicationMessageBuilder().WithTopic("Step").WithPayload("1").Build()); + await managedClient.EnqueueAsync(new MqttApplicationMessageBuilder().WithTopic("Step").WithPayload("2").WithAtLeastOnceQoS().Build()); + + await managedClient.StartAsync(options); + + await managedClient.SubscribeAsync(new TopicFilter("xyz", MqttQualityOfServiceLevel.AtMostOnce)); + + await managedClient.EnqueueAsync(new MqttApplicationMessageBuilder().WithTopic("Step").WithPayload("3").Build()); + + Console.WriteLine("Managed client started."); + Console.ReadLine(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } +} diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index dab82353..0b5cf64e 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -23,22 +23,27 @@ namespace MQTTnet.TestApp.NetCore Console.WriteLine("1 = Start client"); Console.WriteLine("2 = Start server"); Console.WriteLine("3 = Start performance test"); + Console.WriteLine("4 = Start managed client"); + var pressedKey = Console.ReadKey(true); - if (pressedKey.Key == ConsoleKey.D1) + if (pressedKey.KeyChar == '1') { Task.Run(RunClientAsync); - Thread.Sleep(Timeout.Infinite); } - else if (pressedKey.Key == ConsoleKey.D2) + else if (pressedKey.KeyChar == '2') { - Task.Run(() => RunServerAsync()); - Thread.Sleep(Timeout.Infinite); + Task.Run(RunServerAsync); } - else if (pressedKey.Key == ConsoleKey.D3) + else if (pressedKey.KeyChar == '3') { Task.Run(PerformanceTest.RunAsync); - Thread.Sleep(Timeout.Infinite); } + else if (pressedKey.KeyChar == '4') + { + Task.Run(ManagedClientTest.RunAsync); + } + + Thread.Sleep(Timeout.Infinite); } private static async Task RunClientAsync() From 65e6f96e595b013f32a069760c6fda82d181942c Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 21 Oct 2017 16:01:08 +0200 Subject: [PATCH 18/34] Update docs --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 109c98cf..fdd38deb 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,10 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov \* Tested on local machine with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetFramework_. ### Client +* Communication via TCP (+TLS) or WS (WebSocket) supported +* Included core _MqttClient_ with low level functionality +* Also included _ManagedMqttClient_ which maintains the connection and subscriptions automatically. Also application messages are queued and re-scheduled for higher QoS levels automatically. * Rx support (via another project) -* Communication via TCP (+TLS) or WS (WebSocket) ### Server (broker) * List of connected clients available From b52645f77a0e58367c4988176c72a633438c1b1f Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 21 Oct 2017 20:05:25 +0200 Subject: [PATCH 19/34] Add new interface for application message receivers --- .../Client/IApplicationMessageReceiver.cs | 9 ++++++++ MQTTnet.Core/Client/IMqttClient.cs | 3 +-- .../ManagedClient/ManagedMqttClient.cs | 22 +++---------------- 3 files changed, 13 insertions(+), 21 deletions(-) create mode 100644 MQTTnet.Core/Client/IApplicationMessageReceiver.cs diff --git a/MQTTnet.Core/Client/IApplicationMessageReceiver.cs b/MQTTnet.Core/Client/IApplicationMessageReceiver.cs new file mode 100644 index 00000000..2d53cb8a --- /dev/null +++ b/MQTTnet.Core/Client/IApplicationMessageReceiver.cs @@ -0,0 +1,9 @@ +using System; + +namespace MQTTnet.Core.Client +{ + public interface IApplicationMessageReceiver + { + event EventHandler ApplicationMessageReceived; + } +} diff --git a/MQTTnet.Core/Client/IMqttClient.cs b/MQTTnet.Core/Client/IMqttClient.cs index 04b5dcda..9caefbda 100644 --- a/MQTTnet.Core/Client/IMqttClient.cs +++ b/MQTTnet.Core/Client/IMqttClient.cs @@ -5,11 +5,10 @@ using MQTTnet.Core.Packets; namespace MQTTnet.Core.Client { - public interface IMqttClient + public interface IMqttClient : IApplicationMessageReceiver { bool IsConnected { get; } - event EventHandler ApplicationMessageReceived; event EventHandler Connected; event EventHandler Disconnected; diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs index 1a23912e..06120780 100644 --- a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs +++ b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs @@ -12,7 +12,7 @@ using MQTTnet.Core.Protocol; namespace MQTTnet.Core.ManagedClient { - public class ManagedMqttClient + public class ManagedMqttClient : IApplicationMessageReceiver { private readonly ManagedMqttClientStorageManager _storageManager = new ManagedMqttClientStorageManager(); private readonly BlockingCollection _messageQueue = new BlockingCollection(); @@ -38,12 +38,12 @@ namespace MQTTnet.Core.ManagedClient _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; } + public bool IsConnected => _mqttClient.IsConnected; + public event EventHandler Connected; public event EventHandler Disconnected; public event EventHandler ApplicationMessageReceived; - public bool IsConnected => _mqttClient.IsConnected; - public async Task StartAsync(IManagedMqttClientOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -242,22 +242,6 @@ namespace MQTTnet.Core.ManagedClient } } - private async Task SaveAsync(List messages) - { - if (messages == null) - { - return; - } - - var storage = _options.Storage; - if (storage != null) - { - return; - } - - await _options.Storage.SaveQueuedMessagesAsync(messages); - } - private async Task PushSubscriptionsAsync() { _trace.Information(nameof(ManagedMqttClient), "Synchronizing subscriptions"); From 3954777d200922f3bf111f42db9a622d364a769d Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 21 Oct 2017 20:12:38 +0200 Subject: [PATCH 20/34] Refactoring --- .../Serializer/MqttPacketSerializer.cs | 87 ++++--------------- 1 file changed, 17 insertions(+), 70 deletions(-) diff --git a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs b/MQTTnet.Core/Serializer/MqttPacketSerializer.cs index 833d7986..aa4d3b93 100644 --- a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs +++ b/MQTTnet.Core/Serializer/MqttPacketSerializer.cs @@ -48,77 +48,24 @@ namespace MQTTnet.Core.Serializer private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter writer) { - if (packet is MqttConnectPacket connectPacket) + switch (packet) { - return Serialize(connectPacket, writer); - } - - if (packet is MqttConnAckPacket connAckPacket) - { - return Serialize(connAckPacket, writer); - } - - if (packet is MqttDisconnectPacket) - { - return SerializeEmptyPacket(MqttControlPacketType.Disconnect); - } - - if (packet is MqttPingReqPacket) - { - return SerializeEmptyPacket(MqttControlPacketType.PingReq); - } - - if (packet is MqttPingRespPacket) - { - return SerializeEmptyPacket(MqttControlPacketType.PingResp); - } - - if (packet is MqttPublishPacket publishPacket) - { - return Serialize(publishPacket, writer); - } - - if (packet is MqttPubAckPacket pubAckPacket) - { - return Serialize(pubAckPacket, writer); - } - - if (packet is MqttPubRecPacket pubRecPacket) - { - return Serialize(pubRecPacket, writer); - } - - if (packet is MqttPubRelPacket pubRelPacket) - { - return Serialize(pubRelPacket, writer); - } - - if (packet is MqttPubCompPacket pubCompPacket) - { - return Serialize(pubCompPacket, writer); - } - - if (packet is MqttSubscribePacket subscribePacket) - { - return Serialize(subscribePacket, writer); - } - - if (packet is MqttSubAckPacket subAckPacket) - { - return Serialize(subAckPacket, writer); - } - - if (packet is MqttUnsubscribePacket unsubscribePacket) - { - return Serialize(unsubscribePacket, writer); - } - - if (packet is MqttUnsubAckPacket unsubAckPacket) - { - return Serialize(unsubAckPacket, writer); - } - - throw new MqttProtocolViolationException("Packet type invalid."); + case MqttConnectPacket connectPacket: return Serialize(connectPacket, writer); + case MqttConnAckPacket connAckPacket: return Serialize(connAckPacket, writer); + case MqttDisconnectPacket _: return SerializeEmptyPacket(MqttControlPacketType.Disconnect); + case MqttPingReqPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingReq); + case MqttPingRespPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingResp); + case MqttPublishPacket publishPacket: return Serialize(publishPacket, writer); + case MqttPubAckPacket pubAckPacket: return Serialize(pubAckPacket, writer); + case MqttPubRecPacket pubRecPacket: return Serialize(pubRecPacket, writer); + case MqttPubRelPacket pubRelPacket: return Serialize(pubRelPacket, writer); + case MqttPubCompPacket pubCompPacket: return Serialize(pubCompPacket, writer); + case MqttSubscribePacket subscribePacket: return Serialize(subscribePacket, writer); + case MqttSubAckPacket subAckPacket: return Serialize(subAckPacket, writer); + case MqttUnsubscribePacket unsubscribePacket: return Serialize(unsubscribePacket, writer); + case MqttUnsubAckPacket unsubAckPacket: return Serialize(unsubAckPacket, writer); + default: throw new MqttProtocolViolationException("Packet type invalid."); + } } private static MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader) From df1dd0eec7ecc01ac4ad025ea0ce3ab916cb6387 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sat, 21 Oct 2017 20:39:38 +0200 Subject: [PATCH 21/34] Refactoring --- MQTTnet.Core/Server/MqttClientSession.cs | 60 ++++++++++--------- .../Server/MqttTopicFilterComparer.cs | 25 +++----- 2 files changed, 41 insertions(+), 44 deletions(-) diff --git a/MQTTnet.Core/Server/MqttClientSession.cs b/MQTTnet.Core/Server/MqttClientSession.cs index 9b6cce59..969ec015 100644 --- a/MQTTnet.Core/Server/MqttClientSession.cs +++ b/MQTTnet.Core/Server/MqttClientSession.cs @@ -15,7 +15,7 @@ namespace MQTTnet.Core.Server public sealed class MqttClientSession : IDisposable { private readonly HashSet _unacknowledgedPublishPackets = new HashSet(); - + private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager(); private readonly MqttClientSessionsManager _mqttClientSessionsManager; private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue; @@ -186,34 +186,40 @@ namespace MQTTnet.Core.Server await _mqttClientSessionsManager.RetainedMessagesManager.HandleMessageAsync(ClientId, publishPacket); } - if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) + switch (publishPacket.QualityOfServiceLevel) { - _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); - return; + case MqttQualityOfServiceLevel.AtMostOnce: + { + _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); + return; + } + case MqttQualityOfServiceLevel.AtLeastOnce: + { + _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); + + await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, + new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }); + + return; + } + case MqttQualityOfServiceLevel.ExactlyOnce: + { + // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] + lock (_unacknowledgedPublishPackets) + { + _unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier); + } + + _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); + + await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, + new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }); + + return; + } + default: + throw new MqttCommunicationException("Received a not supported QoS level."); } - - if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) - { - _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }); - return; - } - - if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) - { - // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] - lock (_unacknowledgedPublishPackets) - { - _unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier); - } - - _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); - - await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }); - return; - } - - throw new MqttCommunicationException("Received a not supported QoS level."); } private Task HandleIncomingPubRelPacketAsync(IMqttCommunicationAdapter adapter, MqttPubRelPacket pubRelPacket) diff --git a/MQTTnet.Core/Server/MqttTopicFilterComparer.cs b/MQTTnet.Core/Server/MqttTopicFilterComparer.cs index b74852cf..987a71f1 100644 --- a/MQTTnet.Core/Server/MqttTopicFilterComparer.cs +++ b/MQTTnet.Core/Server/MqttTopicFilterComparer.cs @@ -4,7 +4,7 @@ namespace MQTTnet.Core.Server { public static class MqttTopicFilterComparer { - private const char TopicLevelSeparator = '/'; + private static readonly char[] TopicLevelSeparator = { '/' }; public static bool IsMatch(string topic, string filter) { @@ -16,19 +16,15 @@ namespace MQTTnet.Core.Server return true; } - var fragmentsTopic = topic.Split(new[] { TopicLevelSeparator }, StringSplitOptions.None); - var fragmentsFilter = filter.Split(new[] { TopicLevelSeparator }, StringSplitOptions.None); + var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None); + var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None); for (var i = 0; i < fragmentsFilter.Length; i++) { - if (fragmentsFilter[i] == "+") + switch (fragmentsFilter[i]) { - continue; - } - - if (fragmentsFilter[i] == "#" && i == fragmentsFilter.Length - 1) - { - return true; + case "+": continue; + case "#" when i == fragmentsFilter.Length - 1: return true; } if (i >= fragmentsTopic.Length) @@ -36,18 +32,13 @@ namespace MQTTnet.Core.Server return false; } - if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i])) + if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i], StringComparison.Ordinal)) { return false; } } - if (fragmentsTopic.Length > fragmentsFilter.Length) - { - return false; - } - - return true; + return fragmentsTopic.Length <= fragmentsFilter.Length; } } } From 4386ef1e56b26be027fe9e78afc9c5e26e28c792 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 22 Oct 2017 14:19:12 +0200 Subject: [PATCH 22/34] Added interceptor for application messages --- Build/MQTTnet.nuspec | 5 +- .../MqttApplicationMessageExtensions.cs | 13 +-- MQTTnet.Core/MqttApplicationMessage.cs | 13 ++- MQTTnet.Core/MqttApplicationMessageBuilder.cs | 10 ++- .../Serializer/MqttPacketSerializer.cs | 14 ++-- .../MqttClientRetainedMessagesManager.cs | 29 ++++--- MQTTnet.Core/Server/MqttClientSession.cs | 19 +++-- .../Server/MqttClientSessionsManager.cs | 6 +- MQTTnet.Core/Server/MqttServer.cs | 4 +- MQTTnet.Core/Server/MqttServerOptions.cs | 2 + README.md | 1 + .../MqttPacketSerializerTests.cs | 24 +++--- .../PerformanceTest.cs | 12 +-- Tests/MQTTnet.TestApp.NetCore/Program.cs | 74 ++--------------- Tests/MQTTnet.TestApp.NetCore/ServerTest.cs | 81 +++++++++++++++++++ .../MainPage.xaml.cs | 11 +-- 16 files changed, 182 insertions(+), 136 deletions(-) create mode 100644 Tests/MQTTnet.TestApp.NetCore/ServerTest.cs diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index b78712df..50ac4373 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -14,11 +14,12 @@ * [Core] Migrated the trace to a non-static approach (Breaking Change!) * [Core] Added a builder for application messages using a fluent API * [Client] Added a first version of a managed client which will manage the connection, subscription etc. automatically (Thanks to @JTrotta) -* [Server] Added support for WebSockets via ASP.NET Core 2.0 (Thanks to @ChristianRiedl) * [Client] The session state response from the server is now returned in the _ConnectAsync_ method and also part of the _Connected_ event args +* [Server] Added support for WebSockets via ASP.NET Core 2.0 (Thanks to @ChristianRiedl) +* [Server] Added support for a custom application message interceptor Copyright Christian Kratky 2016-2017 - MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M + MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation diff --git a/MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs b/MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs index 7d3c6658..7ca7d005 100644 --- a/MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs +++ b/MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs @@ -6,12 +6,13 @@ namespace MQTTnet.Core.Internal { public static MqttApplicationMessage ToApplicationMessage(this MqttPublishPacket publishPacket) { - return new MqttApplicationMessage( - publishPacket.Topic, - publishPacket.Payload, - publishPacket.QualityOfServiceLevel, - publishPacket.Retain - ); + return new MqttApplicationMessage + { + Topic = publishPacket.Topic, + Payload = publishPacket.Payload, + QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, + Retain = publishPacket.Retain + }; } public static MqttPublishPacket ToPublishPacket(this MqttApplicationMessage applicationMessage) diff --git a/MQTTnet.Core/MqttApplicationMessage.cs b/MQTTnet.Core/MqttApplicationMessage.cs index 382d702c..b07ee18f 100644 --- a/MQTTnet.Core/MqttApplicationMessage.cs +++ b/MQTTnet.Core/MqttApplicationMessage.cs @@ -5,6 +5,11 @@ namespace MQTTnet.Core { public sealed class MqttApplicationMessage { + public MqttApplicationMessage() + { + } + + [Obsolete("Use object initializer or _MqttApplicationMessageBuilder_ instead.")] public MqttApplicationMessage(string topic, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain) { Topic = topic ?? throw new ArgumentNullException(nameof(topic)); @@ -13,12 +18,12 @@ namespace MQTTnet.Core Retain = retain; } - public string Topic { get; } + public string Topic { get; set; } - public byte[] Payload { get; } + public byte[] Payload { get; set; } - public MqttQualityOfServiceLevel QualityOfServiceLevel { get; } + public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } - public bool Retain { get; } + public bool Retain { get; set; } } } diff --git a/MQTTnet.Core/MqttApplicationMessageBuilder.cs b/MQTTnet.Core/MqttApplicationMessageBuilder.cs index 9905b098..c2e04318 100644 --- a/MQTTnet.Core/MqttApplicationMessageBuilder.cs +++ b/MQTTnet.Core/MqttApplicationMessageBuilder.cs @@ -13,7 +13,7 @@ namespace MQTTnet.Core private string _topic; private byte[] _payload; private bool _retain; - + public MqttApplicationMessageBuilder WithTopic(string topic) { _topic = topic; @@ -102,7 +102,13 @@ namespace MQTTnet.Core throw new MqttProtocolViolationException("Topic is not set."); } - return new MqttApplicationMessage(_topic, _payload ?? new byte[0], _qualityOfServiceLevel, _retain); + return new MqttApplicationMessage + { + Topic = _topic, + Payload = _payload ?? new byte[0], + QualityOfServiceLevel = _qualityOfServiceLevel, + Retain = _retain + }; } } } diff --git a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs b/MQTTnet.Core/Serializer/MqttPacketSerializer.cs index aa4d3b93..00906f4a 100644 --- a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs +++ b/MQTTnet.Core/Serializer/MqttPacketSerializer.cs @@ -65,7 +65,7 @@ namespace MQTTnet.Core.Serializer case MqttUnsubscribePacket unsubscribePacket: return Serialize(unsubscribePacket, writer); case MqttUnsubAckPacket unsubAckPacket: return Serialize(unsubAckPacket, writer); default: throw new MqttProtocolViolationException("Packet type invalid."); - } + } } private static MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader) @@ -233,11 +233,13 @@ namespace MQTTnet.Core.Serializer if (willFlag) { - packet.WillMessage = new MqttApplicationMessage( - reader.ReadStringWithLengthPrefix(), - reader.ReadWithLengthPrefix(), - (MqttQualityOfServiceLevel)willQoS, - willRetain); + packet.WillMessage = new MqttApplicationMessage + { + Topic = reader.ReadStringWithLengthPrefix(), + Payload = reader.ReadWithLengthPrefix(), + QualityOfServiceLevel = (MqttQualityOfServiceLevel)willQoS, + Retain = willRetain + }; } if (usernameFlag) diff --git a/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs b/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs index fe95f019..22de3054 100644 --- a/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs +++ b/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs @@ -3,15 +3,14 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Internal; using MQTTnet.Core.Packets; namespace MQTTnet.Core.Server { public sealed class MqttClientRetainedMessagesManager { + private readonly Dictionary _retainedMessages = new Dictionary(); private readonly MqttNetTrace _trace; - private readonly Dictionary _retainedMessages = new Dictionary(); private readonly MqttServerOptions _options; public MqttClientRetainedMessagesManager(MqttServerOptions options, MqttNetTrace trace) @@ -35,7 +34,7 @@ namespace MQTTnet.Core.Server _retainedMessages.Clear(); foreach (var retainedMessage in retainedMessages) { - _retainedMessages[retainedMessage.Topic] = retainedMessage.ToPublishPacket(); + _retainedMessages[retainedMessage.Topic] = retainedMessage; } } } @@ -45,25 +44,25 @@ namespace MQTTnet.Core.Server } } - public async Task HandleMessageAsync(string clientId, MqttPublishPacket publishPacket) + public async Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage) { - if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - List allRetainedMessages; + List allRetainedMessages; lock (_retainedMessages) { - if (publishPacket.Payload?.Any() == false) + if (applicationMessage.Payload?.Any() == false) { - _retainedMessages.Remove(publishPacket.Topic); - _trace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' cleared retained message for topic '{1}'.", clientId, publishPacket.Topic); + _retainedMessages.Remove(applicationMessage.Topic); + _trace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); } else { - _retainedMessages[publishPacket.Topic] = publishPacket; - _trace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' updated retained message for topic '{1}'.", clientId, publishPacket.Topic); + _retainedMessages[applicationMessage.Topic] = applicationMessage; + _trace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' updated retained message for topic '{1}'.", clientId, applicationMessage.Topic); } - allRetainedMessages = new List(_retainedMessages.Values); + allRetainedMessages = new List(_retainedMessages.Values); } try @@ -71,7 +70,7 @@ namespace MQTTnet.Core.Server // ReSharper disable once UseNullPropagation if (_options.Storage != null) { - await _options.Storage.SaveRetainedMessagesAsync(allRetainedMessages.Select(p => p.ToApplicationMessage()).ToList()); + await _options.Storage.SaveRetainedMessagesAsync(allRetainedMessages); } } catch (Exception exception) @@ -80,9 +79,9 @@ namespace MQTTnet.Core.Server } } - public List GetMessages(MqttSubscribePacket subscribePacket) + public List GetMessages(MqttSubscribePacket subscribePacket) { - var retainedMessages = new List(); + var retainedMessages = new List(); lock (_retainedMessages) { foreach (var retainedMessage in _retainedMessages.Values) diff --git a/MQTTnet.Core/Server/MqttClientSession.cs b/MQTTnet.Core/Server/MqttClientSession.cs index 969ec015..f913901e 100644 --- a/MQTTnet.Core/Server/MqttClientSession.cs +++ b/MQTTnet.Core/Server/MqttClientSession.cs @@ -72,7 +72,7 @@ namespace MQTTnet.Core.Server { if (_willMessage != null) { - _mqttClientSessionsManager.DispatchPublishPacket(this, _willMessage.ToPublishPacket()); + _mqttClientSessionsManager.DispatchApplicationMessage(this, _willMessage); } _cancellationTokenSource?.Cancel(false); @@ -175,27 +175,30 @@ namespace MQTTnet.Core.Server var retainedMessages = _mqttClientSessionsManager.RetainedMessagesManager.GetMessages(subscribePacket); foreach (var publishPacket in retainedMessages) { - EnqueuePublishPacket(publishPacket); + EnqueuePublishPacket(publishPacket.ToPublishPacket()); } } private async Task HandleIncomingPublishPacketAsync(IMqttCommunicationAdapter adapter, MqttPublishPacket publishPacket) { - if (publishPacket.Retain) + var applicationMessage = publishPacket.ToApplicationMessage(); + _options.ApplicationMessageInterceptor?.Invoke(applicationMessage); + + if (applicationMessage.Retain) { - await _mqttClientSessionsManager.RetainedMessagesManager.HandleMessageAsync(ClientId, publishPacket); + await _mqttClientSessionsManager.RetainedMessagesManager.HandleMessageAsync(ClientId, applicationMessage); } - switch (publishPacket.QualityOfServiceLevel) + switch (applicationMessage.QualityOfServiceLevel) { case MqttQualityOfServiceLevel.AtMostOnce: { - _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); + _mqttClientSessionsManager.DispatchApplicationMessage(this, applicationMessage); return; } case MqttQualityOfServiceLevel.AtLeastOnce: { - _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); + _mqttClientSessionsManager.DispatchApplicationMessage(this, applicationMessage); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }); @@ -210,7 +213,7 @@ namespace MQTTnet.Core.Server _unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier); } - _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); + _mqttClientSessionsManager.DispatchApplicationMessage(this, applicationMessage); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }); diff --git a/MQTTnet.Core/Server/MqttClientSessionsManager.cs b/MQTTnet.Core/Server/MqttClientSessionsManager.cs index 3a93d089..aedd3d76 100644 --- a/MQTTnet.Core/Server/MqttClientSessionsManager.cs +++ b/MQTTnet.Core/Server/MqttClientSessionsManager.cs @@ -117,11 +117,11 @@ namespace MQTTnet.Core.Server } } - public void DispatchPublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket) + public void DispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) { try { - var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, publishPacket.ToApplicationMessage()); + var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, applicationMessage); ApplicationMessageReceived?.Invoke(this, eventArgs); } catch (Exception exception) @@ -133,7 +133,7 @@ namespace MQTTnet.Core.Server { foreach (var clientSession in _clientSessions.Values.ToList()) { - clientSession.EnqueuePublishPacket(publishPacket); + clientSession.EnqueuePublishPacket(applicationMessage.ToPublishPacket()); } } } diff --git a/MQTTnet.Core/Server/MqttServer.cs b/MQTTnet.Core/Server/MqttServer.cs index 7da59575..341b3701 100644 --- a/MQTTnet.Core/Server/MqttServer.cs +++ b/MQTTnet.Core/Server/MqttServer.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Adapter; using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Internal; namespace MQTTnet.Core.Server { @@ -42,7 +41,8 @@ namespace MQTTnet.Core.Server { if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - _clientSessionsManager.DispatchPublishPacket(null, applicationMessage.ToPublishPacket()); + _options.ApplicationMessageInterceptor?.Invoke(applicationMessage); + _clientSessionsManager.DispatchApplicationMessage(null, applicationMessage); } public async Task StartAsync() diff --git a/MQTTnet.Core/Server/MqttServerOptions.cs b/MQTTnet.Core/Server/MqttServerOptions.cs index c3673b9d..27dc21c5 100644 --- a/MQTTnet.Core/Server/MqttServerOptions.cs +++ b/MQTTnet.Core/Server/MqttServerOptions.cs @@ -16,6 +16,8 @@ namespace MQTTnet.Core.Server public Func ConnectionValidator { get; set; } + public Action ApplicationMessageInterceptor { get; set; } + public IMqttServerStorage Storage { get; set; } } } diff --git a/README.md b/README.md index fdd38deb..bc7c13b1 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * Extensible client credential validation * Retained messages are supported including persisting via interface methods (own implementation required) * WebSockets supported (via ASP.NET Core 2.0) +* A custom message interceptor can be added which allows transforming or extending every received application message # Supported frameworks * .NET Standard 1.3+ diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs index c78e3aee..bdd07516 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs @@ -53,11 +53,13 @@ namespace MQTTnet.Core.Tests Username = "USER", KeepAlivePeriod = 123, CleanSession = true, - WillMessage = new MqttApplicationMessage( - "My/last/will", - Encoding.UTF8.GetBytes("Good byte."), - MqttQualityOfServiceLevel.AtLeastOnce, - true) + WillMessage = new MqttApplicationMessage + { + Topic = "My/last/will", + Payload = Encoding.UTF8.GetBytes("Good byte."), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, + Retain = true + } }; SerializeAndCompare(p, "EDUABE1RVFQE7gB7AANYWVoADE15L2xhc3Qvd2lsbAAKR29vZCBieXRlLgAEVVNFUgAEUEFTUw=="); @@ -88,11 +90,13 @@ namespace MQTTnet.Core.Tests Username = "USER", KeepAlivePeriod = 123, CleanSession = true, - WillMessage = new MqttApplicationMessage( - "My/last/will", - Encoding.UTF8.GetBytes("Good byte."), - MqttQualityOfServiceLevel.AtLeastOnce, - true) + WillMessage = new MqttApplicationMessage + { + Topic = "My/last/will", + Payload = Encoding.UTF8.GetBytes("Good byte."), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, + Retain = true + } }; DeserializeAndCompare(p, "EDUABE1RVFQE7gB7AANYWVoADE15L2xhc3Qvd2lsbAAKR29vZCBieXRlLgAEVVNFUgAEUEFTUw=="); diff --git a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index f8b8c33b..638bdc0a 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -151,12 +151,12 @@ namespace MQTTnet.TestApp.NetCore private static MqttApplicationMessage CreateMessage() { - return new MqttApplicationMessage( - "A/B/C", - Encoding.UTF8.GetBytes("Hello World"), - MqttQualityOfServiceLevel.AtMostOnce, - false - ); + return new MqttApplicationMessage + { + Topic = "A/B/C", + Payload = Encoding.UTF8.GetBytes("Hello World"), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce + }; } private static Task PublishSingleMessage(IMqttClient client, MqttApplicationMessage applicationMessage, ref int count) diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 0b5cf64e..63c4ee24 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -32,7 +32,7 @@ namespace MQTTnet.TestApp.NetCore } else if (pressedKey.KeyChar == '2') { - Task.Run(RunServerAsync); + Task.Run(ServerTest.RunAsync); } else if (pressedKey.KeyChar == '3') { @@ -119,12 +119,12 @@ namespace MQTTnet.TestApp.NetCore { Console.ReadLine(); - var applicationMessage = new MqttApplicationMessage( - "A/B/C", - Encoding.UTF8.GetBytes("Hello World"), - MqttQualityOfServiceLevel.AtLeastOnce, - false - ); + var applicationMessage = new MqttApplicationMessage + { + Topic = "A/B/C", + Payload = Encoding.UTF8.GetBytes("Hello World"), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce + }; await client.PublishAsync(applicationMessage); } @@ -135,66 +135,6 @@ namespace MQTTnet.TestApp.NetCore } } - private static Task RunServerAsync() - { - MqttNetTrace.TraceMessagePublished += (s, e) => - { - Console.WriteLine($">> [{e.TraceMessage.Timestamp:O}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}"); - if (e.TraceMessage.Exception != null) - { - Console.WriteLine(e.TraceMessage.Exception); - } - }; - - try - { - var options = new MqttServerOptions - { - ConnectionValidator = p => - { - if (p.ClientId == "SpecialClient") - { - if (p.Username != "USER" || p.Password != "PASS") - { - return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; - } - } - - return MqttConnectReturnCode.ConnectionAccepted; - } - }; - - options.Storage = new RetainedMessageHandler(); - - - //var certificate = new X509Certificate(@"C:\certs\test\test.cer", ""); - //options.TlsEndpointOptions.Certificate = certificate.Export(X509ContentType.Cert); - //options.ConnectionBacklog = 5; - //options.DefaultEndpointOptions.IsEnabled = true; - //options.TlsEndpointOptions.IsEnabled = false; - - var mqttServer = new MqttServerFactory().CreateMqttServer(options); - mqttServer.ClientDisconnected += (s, e) => - { - Console.Write("Client disconnected event fired."); - }; - - mqttServer.StartAsync(); - - Console.WriteLine("Press any key to exit."); - Console.ReadLine(); - - mqttServer.StopAsync(); - } - catch (Exception e) - { - Console.WriteLine(e); - } - - Console.ReadLine(); - return Task.FromResult(0); - } - // ReSharper disable once UnusedMember.Local private static async void WikiCode() { diff --git a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs new file mode 100644 index 00000000..639920fc --- /dev/null +++ b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs @@ -0,0 +1,81 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using MQTTnet.Core.Diagnostics; +using MQTTnet.Core.Protocol; +using MQTTnet.Core.Server; + +namespace MQTTnet.TestApp.NetCore +{ + public static class ServerTest + { + public static Task RunAsync() + { + MqttNetTrace.TraceMessagePublished += (s, e) => + { + Console.WriteLine($">> [{e.TraceMessage.Timestamp:O}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}"); + if (e.TraceMessage.Exception != null) + { + Console.WriteLine(e.TraceMessage.Exception); + } + }; + + try + { + var options = new MqttServerOptions + { + ConnectionValidator = p => + { + if (p.ClientId == "SpecialClient") + { + if (p.Username != "USER" || p.Password != "PASS") + { + return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; + } + } + + return MqttConnectReturnCode.ConnectionAccepted; + } + }; + + options.Storage = new RetainedMessageHandler(); + + options.ApplicationMessageInterceptor = message => + { + if (MqttTopicFilterComparer.IsMatch(message.Topic, "/myTopic/WithTimestamp/#")) + { + // Replace the payload with the timestamp. But also extending a JSON + // based payload with the timestamp is a suitable use case. + message.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")); + } + }; + + //var certificate = new X509Certificate(@"C:\certs\test\test.cer", ""); + //options.TlsEndpointOptions.Certificate = certificate.Export(X509ContentType.Cert); + //options.ConnectionBacklog = 5; + //options.DefaultEndpointOptions.IsEnabled = true; + //options.TlsEndpointOptions.IsEnabled = false; + + var mqttServer = new MqttServerFactory().CreateMqttServer(options); + mqttServer.ClientDisconnected += (s, e) => + { + Console.Write("Client disconnected event fired."); + }; + + mqttServer.StartAsync(); + + Console.WriteLine("Press any key to exit."); + Console.ReadLine(); + + mqttServer.StopAsync(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + + Console.ReadLine(); + return Task.FromResult(0); + } + } +} diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 8826e135..981d4a01 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -119,11 +119,12 @@ namespace MQTTnet.TestApp.UniversalWindows payload = Convert.FromBase64String(Payload.Text); } - var message = new MqttApplicationMessage( - Topic.Text, - payload, - qos, - Retain.IsChecked == true); + var message = new MqttApplicationMessageBuilder() + .WithTopic(Topic.Text) + .WithPayload(payload) + .WithQualityOfServiceLevel(qos) + .WithRetainFlag(Retain.IsChecked == true) + .Build(); await _mqttClient.PublishAsync(message); } From 3f080e04b9edca721b724362dd9b58741d23d3aa Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 22 Oct 2017 14:44:06 +0200 Subject: [PATCH 23/34] Add topic filter builder --- MQTTnet.Core/Server/MqttServerOptions.cs | 2 +- MQTTnet.Core/{Packets => }/TopicFilter.cs | 2 +- MQTTnet.Core/TopicFilterBuilder.cs | 51 +++++++++++++++++++++ README.md | 1 + Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 49 +++++++++++++++++--- Tests/MQTTnet.TestApp.NetCore/ServerTest.cs | 2 + 6 files changed, 99 insertions(+), 8 deletions(-) rename MQTTnet.Core/{Packets => }/TopicFilter.cs (95%) create mode 100644 MQTTnet.Core/TopicFilterBuilder.cs diff --git a/MQTTnet.Core/Server/MqttServerOptions.cs b/MQTTnet.Core/Server/MqttServerOptions.cs index 27dc21c5..4c6f7adc 100644 --- a/MQTTnet.Core/Server/MqttServerOptions.cs +++ b/MQTTnet.Core/Server/MqttServerOptions.cs @@ -16,7 +16,7 @@ namespace MQTTnet.Core.Server public Func ConnectionValidator { get; set; } - public Action ApplicationMessageInterceptor { get; set; } + public Func ApplicationMessageInterceptor { get; set; } public IMqttServerStorage Storage { get; set; } } diff --git a/MQTTnet.Core/Packets/TopicFilter.cs b/MQTTnet.Core/TopicFilter.cs similarity index 95% rename from MQTTnet.Core/Packets/TopicFilter.cs rename to MQTTnet.Core/TopicFilter.cs index 6786b1d7..3e95afa0 100644 --- a/MQTTnet.Core/Packets/TopicFilter.cs +++ b/MQTTnet.Core/TopicFilter.cs @@ -1,6 +1,6 @@ using MQTTnet.Core.Protocol; -namespace MQTTnet.Core.Packets +namespace MQTTnet.Core { public sealed class TopicFilter { diff --git a/MQTTnet.Core/TopicFilterBuilder.cs b/MQTTnet.Core/TopicFilterBuilder.cs new file mode 100644 index 00000000..90114e7c --- /dev/null +++ b/MQTTnet.Core/TopicFilterBuilder.cs @@ -0,0 +1,51 @@ +using MQTTnet.Core.Protocol; +using MQTTnet.Core.Exceptions; + +namespace MQTTnet.Core +{ + public class TopicFilterBuilder + { + private string _topic; + private MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; + + public TopicFilterBuilder WithTopic(string topic) + { + _topic = topic; + return this; + } + + public TopicFilterBuilder WithQualityOfServiceLevel(MqttQualityOfServiceLevel qualityOfServiceLevel) + { + _qualityOfServiceLevel = qualityOfServiceLevel; + return this; + } + + public TopicFilterBuilder WithAtLeastOnceQoS() + { + _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce; + return this; + } + + public TopicFilterBuilder WithAtMostOnceQoS() + { + _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; + return this; + } + + public TopicFilterBuilder WithExactlyOnceQoS() + { + _qualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce; + return this; + } + + public TopicFilter Build() + { + if (string.IsNullOrEmpty(_topic)) + { + throw new MqttProtocolViolationException("Topic is not set."); + } + + return new TopicFilter(_topic, _qualityOfServiceLevel); + } + } +} diff --git a/README.md b/README.md index bc7c13b1..2bd607a8 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * Included core _MqttClient_ with low level functionality * Also included _ManagedMqttClient_ which maintains the connection and subscriptions automatically. Also application messages are queued and re-scheduled for higher QoS levels automatically. * Rx support (via another project) +* Compatible with Microsoft Azure IoT Hub ### Server (broker) * List of connected clients available diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index 63ef0bb7..948497ee 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Text; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Core.Adapter; @@ -53,7 +55,7 @@ namespace MQTTnet.Core.Tests var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); await s.StartAsync(); - var willMessage = new MqttApplicationMessage("My/last/will", new byte[0], MqttQualityOfServiceLevel.AtMostOnce, false); + var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c2 = await serverAdapter.ConnectTestClient(s, "c2", willMessage); @@ -83,7 +85,7 @@ namespace MQTTnet.Core.Tests var receivedMessagesCount = 0; c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; - var message = new MqttApplicationMessage("a", new byte[0], MqttQualityOfServiceLevel.AtLeastOnce, false); + var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); await c2.PublishAsync(message); Assert.AreEqual(0, receivedMessagesCount); @@ -118,7 +120,7 @@ namespace MQTTnet.Core.Tests var receivedMessagesCount = 0; c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; - var message = new MqttApplicationMessage("a", new byte[0], MqttQualityOfServiceLevel.AtLeastOnce, false); + var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce)); s.Publish(message); @@ -229,6 +231,41 @@ namespace MQTTnet.Core.Tests Assert.AreEqual(1, receivedMessagesCount); } + [TestMethod] + public async Task MqttServer_InterceptMessage() + { + var options = new MqttServerOptions + { + ApplicationMessageInterceptor = message => + { + message.Payload = Encoding.ASCII.GetBytes("extended"); + return message; + } + }; + + var serverAdapter = new TestMqttServerAdapter(); + var s = new MqttServer(options, new List { serverAdapter }, new MqttNetTrace()); + await s.StartAsync(); + + var c1 = await serverAdapter.ConnectTestClient(s, "c1"); + var c2 = await serverAdapter.ConnectTestClient(s, "c2"); + await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("test").Build()); + + var isIntercepted = false; + c2.ApplicationMessageReceived += (sender, args) => + { + isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(args.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; + }; + + var m = new MqttApplicationMessageBuilder().WithTopic("test").Build(); + await c1.PublishAsync(m); + await c1.DisconnectAsync(); + + await Task.Delay(500); + + Assert.IsTrue(isIntercepted); + } + private class TestStorage : IMqttServerStorage { private IList _messages = new List(); @@ -262,8 +299,8 @@ namespace MQTTnet.Core.Tests var receivedMessagesCount = 0; c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; - await c1.SubscribeAsync(new TopicFilter(topicFilter, filterQualityOfServiceLevel)); - await c2.PublishAsync(new MqttApplicationMessage(topic, new byte[0], qualityOfServiceLevel, false)); + await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic(topicFilter).WithQualityOfServiceLevel(filterQualityOfServiceLevel).Build()); + await c2.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(new byte[0]).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); await Task.Delay(500); await c1.UnsubscribeAsync(topicFilter); diff --git a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs index 639920fc..30d9596d 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs @@ -48,6 +48,8 @@ namespace MQTTnet.TestApp.NetCore // based payload with the timestamp is a suitable use case. message.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")); } + + return message; }; //var certificate = new X509Certificate(@"C:\certs\test\test.cer", ""); From 41edc3a1ece8ae8b1ab9c893082d717e86d2c103 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 22 Oct 2017 14:44:57 +0200 Subject: [PATCH 24/34] Update docs --- Build/MQTTnet.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 50ac4373..e6ff10f3 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -15,6 +15,7 @@ * [Core] Added a builder for application messages using a fluent API * [Client] Added a first version of a managed client which will manage the connection, subscription etc. automatically (Thanks to @JTrotta) * [Client] The session state response from the server is now returned in the _ConnectAsync_ method and also part of the _Connected_ event args +* [Client] Added a _TopicFilterBuilder_ using a fluent API (Namespace Changes!) * [Server] Added support for WebSockets via ASP.NET Core 2.0 (Thanks to @ChristianRiedl) * [Server] Added support for a custom application message interceptor From d10f29bbc6c406e61c7f0856879fee2da042b296 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sun, 22 Oct 2017 19:00:50 +0200 Subject: [PATCH 25/34] initial version --- .../MqttCommunicationAdapterFactory.cs | 30 ----- .../Implementations/MqttServerAdapter.cs | 23 ++-- .../Implementations/MqttTcpChannel.cs | 2 +- .../MQTTnet.Netstandard.csproj | 7 +- .../MQTTnet.NetStandard/MqttClientFactory.cs | 20 ---- Frameworks/MQTTnet.NetStandard/MqttFactory.cs | 104 ++++++++++++++++++ .../MQTTnet.NetStandard/MqttServerFactory.cs | 20 ---- .../ServiceCollectionExtensions.cs | 55 +++++++++ .../IMqttCommunicationAdapterFactory.cs | 12 ++ .../MqttChannelCommunicationAdapter.cs | 12 +- MQTTnet.Core/Client/IMqttClientFactory.cs | 7 +- .../IMqttCommunicationAdapterFactory.cs | 9 -- MQTTnet.Core/Client/MqttClient.cs | 52 ++++----- MQTTnet.Core/Client/MqttPacketDispatcher.cs | 12 +- .../Diagnostics/IMqttNetTraceHandler.cs | 9 -- MQTTnet.Core/Diagnostics/MqttNetTrace.cs | 77 ------------- MQTTnet.Core/Diagnostics/MqttNetTraceLevel.cs | 10 -- .../Diagnostics/MqttNetTraceMessage.cs | 29 ----- .../MqttNetTraceMessagePublishedEventArgs.cs | 14 --- MQTTnet.Core/MQTTnet.Core.csproj | 5 + .../ManagedClient/ManagedMqttClient.cs | 29 +++-- .../Server/IMqttClientSesssionFactory.cs | 7 ++ MQTTnet.Core/Server/IMqttServerFactory.cs | 6 +- .../Server/MqttClientPendingMessagesQueue.cs | 16 +-- .../MqttClientRetainedMessagesManager.cs | 19 ++-- MQTTnet.Core/Server/MqttClientSession.cs | 27 ++--- .../Server/MqttClientSessionsManager.cs | 46 ++++---- MQTTnet.Core/Server/MqttServer.cs | 29 +++-- .../MQTTnet.Core.Tests.csproj | 1 + .../MqttCommunicationAdapterFactory.cs | 8 +- Tests/MQTTnet.Core.Tests/MqttServerTests.cs | 67 +++++++++-- .../ServiceCollectionTest.cs | 30 +++++ .../TestClientSessionFactory.cs | 12 ++ Tests/MQTTnet.Core.Tests/TestLogger.cs | 22 ++++ .../TestMqttServerAdapter.cs | 3 +- .../MqttWebSocketServerAdapter.cs | 12 +- Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs | 20 +--- .../MQTTnet.TestApp.NetCore.csproj | 9 +- .../ManagedClientTest.cs | 21 ++-- .../PerformanceTest.cs | 73 ++++++------ Tests/MQTTnet.TestApp.NetCore/Program.cs | 53 +++------ Tests/MQTTnet.TestApp.NetCore/ServerTest.cs | 48 ++++---- 42 files changed, 580 insertions(+), 487 deletions(-) delete mode 100644 Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs delete mode 100644 Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs create mode 100644 Frameworks/MQTTnet.NetStandard/MqttFactory.cs delete mode 100644 Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs create mode 100644 Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs create mode 100644 MQTTnet.Core/Adapter/IMqttCommunicationAdapterFactory.cs delete mode 100644 MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs delete mode 100644 MQTTnet.Core/Diagnostics/IMqttNetTraceHandler.cs delete mode 100644 MQTTnet.Core/Diagnostics/MqttNetTrace.cs delete mode 100644 MQTTnet.Core/Diagnostics/MqttNetTraceLevel.cs delete mode 100644 MQTTnet.Core/Diagnostics/MqttNetTraceMessage.cs delete mode 100644 MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs create mode 100644 MQTTnet.Core/Server/IMqttClientSesssionFactory.cs create mode 100644 Tests/MQTTnet.Core.Tests/ServiceCollectionTest.cs create mode 100644 Tests/MQTTnet.Core.Tests/TestClientSessionFactory.cs create mode 100644 Tests/MQTTnet.Core.Tests/TestLogger.cs diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs deleted file mode 100644 index 0654e27b..00000000 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using MQTTnet.Core.Adapter; -using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Serializer; - -namespace MQTTnet.Implementations -{ - public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory - { - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - - if (options is MqttClientTcpOptions tcpOptions) - { - var trace = new MqttNetTrace(); - return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }, trace); - } - - if (options is MqttClientWebSocketOptions webSocketOptions) - { - var trace = new MqttNetTrace(); - return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }, trace); - } - - throw new NotSupportedException(); - } - } -} \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs index 4b0c178c..2d3db159 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs @@ -7,23 +7,26 @@ using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Serializer; using MQTTnet.Core.Server; +using Microsoft.Extensions.Logging; +using MQTTnet.Core.Client; namespace MQTTnet.Implementations { public class MqttServerAdapter : IMqttServerAdapter, IDisposable { - private readonly MqttNetTrace _trace; + private readonly ILogger _logger; + private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory; private CancellationTokenSource _cancellationTokenSource; private Socket _defaultEndpointSocket; private Socket _tlsEndpointSocket; private X509Certificate2 _tlsCertificate; - public MqttServerAdapter(MqttNetTrace trace) + public MqttServerAdapter(ILogger logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory) { - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory)); } public event EventHandler ClientAccepted; @@ -95,17 +98,17 @@ namespace MQTTnet.Implementations try { //todo: else branch can be used with min dependency NET46 -#if NET45 +#if NET451 var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false); #else var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false); #endif - var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _trace); + var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(clientSocket, null)); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) { - _trace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint."); + _logger.LogError(new EventId(), exception, "Error while accepting connection at default endpoint."); //excessive CPU consumed if in endless loop of socket errors await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); @@ -119,7 +122,7 @@ namespace MQTTnet.Implementations { try { -#if NET45 +#if NET451 var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null).ConfigureAwait(false); #else var clientSocket = await _tlsEndpointSocket.AcceptAsync().ConfigureAwait(false); @@ -128,12 +131,12 @@ namespace MQTTnet.Implementations var sslStream = new SslStream(new NetworkStream(clientSocket)); await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); - var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _trace); + var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream)); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) { - _trace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint."); + _logger.LogError(new EventId(), exception, "Error while accepting connection at TLS endpoint."); //excessive CPU consumed if in endless loop of socket errors await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs index cd9151e6..7c051bfd 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs @@ -59,7 +59,7 @@ namespace MQTTnet.Implementations } //todo: else brach can be used with min dependency NET46 -#if NET45 +#if NET451 await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false); #else await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false); diff --git a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj index 88f25147..f8dbcc9f 100644 --- a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj +++ b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj @@ -1,7 +1,7 @@  - netstandard1.3;net45 + netstandard1.3;net451 MQTTnet MQTTnet 2.5.0.0 @@ -18,6 +18,11 @@ + + + + + diff --git a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs deleted file mode 100644 index fd5da681..00000000 --- a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.ManagedClient; -using MQTTnet.Implementations; - -namespace MQTTnet -{ - public class MqttClientFactory : IMqttClientFactory - { - public IMqttClient CreateMqttClient(IMqttNetTraceHandler traceHandler = null) - { - return new MqttClient(new MqttCommunicationAdapterFactory(), new MqttNetTrace(traceHandler)); - } - - public ManagedMqttClient CreateManagedMqttClient(IMqttNetTraceHandler traceHandler = null) - { - return new ManagedMqttClient(new MqttCommunicationAdapterFactory(), new MqttNetTrace()); - } - } -} \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs new file mode 100644 index 00000000..3ac03d20 --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs @@ -0,0 +1,104 @@ +using System; +using MQTTnet.Core.Adapter; +using MQTTnet.Core.Client; +using MQTTnet.Core.Serializer; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using MQTTnet.Implementations; +using MQTTnet.Core.ManagedClient; +using MQTTnet.Core.Server; +using System.Linq; +using MQTTnet.Core.Channel; + +namespace MQTTnet +{ + public class MqttFactory : IMqttCommunicationAdapterFactory, IMqttClientSesssionFactory, IMqttClientFactory, IMqttServerFactory + { + private readonly IServiceProvider _serviceProvider; + + private static IServiceProvider BuildServiceProvider() + { + return new ServiceCollection() + .AddMqttClient() + .AddMqttServer() + .AddLogging() + .BuildServiceProvider(); + } + + public MqttFactory() + : this(BuildServiceProvider()) + { + } + + public MqttFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public IMqttCommunicationAdapter CreateClientMqttCommunicationAdapter(IMqttClientOptions options) + { + var logger = _serviceProvider.GetRequiredService>(); + return new MqttChannelCommunicationAdapter(CreateMqttCommunicationChannel(options), CreateSerializer(options.ProtocolVersion), logger); + } + + public IMqttCommunicationAdapter CreateServerMqttCommunicationAdapter(IMqttCommunicationChannel channel) + { + var serializer = _serviceProvider.GetRequiredService(); + var logger = _serviceProvider.GetRequiredService>(); + return new MqttChannelCommunicationAdapter(channel, serializer, logger); + } + + public IMqttCommunicationChannel CreateMqttCommunicationChannel(IMqttClientOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + switch (options) + { + case MqttClientTcpOptions tcpOptions: + return CreateTcpChannel(tcpOptions); + case MqttClientWebSocketOptions webSocketOptions: + return CreateWebSocketChannel(webSocketOptions); + default: + throw new NotSupportedException(); + } + } + + public MqttTcpChannel CreateTcpChannel(MqttClientTcpOptions tcpOptions) + { + return new MqttTcpChannel(tcpOptions); + } + + public MqttWebSocketChannel CreateWebSocketChannel(MqttClientWebSocketOptions webSocketOptions) + { + return new MqttWebSocketChannel(webSocketOptions); + } + + public MqttPacketSerializer CreateSerializer(MqttProtocolVersion protocolVersion) + { + return new MqttPacketSerializer() + { + ProtocolVersion = protocolVersion + }; + } + + public MqttClientSession CreateClientSession(string sessionId, MqttClientSessionsManager mqttClientSessionsManager) + { + return new MqttClientSession(sessionId, mqttClientSessionsManager, _serviceProvider.GetRequiredService>(), _serviceProvider.GetRequiredService>()); + } + + public IMqttClient CreateMqttClient() + { + return _serviceProvider.GetRequiredService(); + } + + public ManagedMqttClient CreateManagedMqttClient() + { + return _serviceProvider.GetRequiredService(); + } + + public IMqttServer CreateMqttServer() + { + return _serviceProvider.GetRequiredService(); + } + } +} \ No newline at end of file diff --git a/Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs deleted file mode 100644 index 468b8e08..00000000 --- a/Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Server; -using MQTTnet.Implementations; - -namespace MQTTnet -{ - public class MqttServerFactory : IMqttServerFactory - { - public IMqttServer CreateMqttServer(MqttServerOptions options, IMqttNetTraceHandler traceHandler = null) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - - var trace = new MqttNetTrace(traceHandler); - return new MqttServer(options, new List { new MqttServerAdapter(trace) }, trace); - } - } -} diff --git a/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs b/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..c0578826 --- /dev/null +++ b/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.DependencyInjection; +using MQTTnet.Core.Adapter; +using MQTTnet.Core.Client; +using MQTTnet.Core.ManagedClient; +using MQTTnet.Core.Serializer; +using MQTTnet.Core.Server; +using MQTTnet.Implementations; +using System; + +namespace MQTTnet +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddMqttServer(this IServiceCollection services) + { + services.AddOptions(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + + services.AddSingleton(); + services.AddSingleton(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + return services; + } + + public static IServiceCollection AddMqttServer(this IServiceCollection services, Action configureOptions) + { + return services + .AddMqttServer() + .Configure(configureOptions); + } + + public static IServiceCollection AddMqttClient(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + return services; + } + } +} diff --git a/MQTTnet.Core/Adapter/IMqttCommunicationAdapterFactory.cs b/MQTTnet.Core/Adapter/IMqttCommunicationAdapterFactory.cs new file mode 100644 index 00000000..ffcfd2c1 --- /dev/null +++ b/MQTTnet.Core/Adapter/IMqttCommunicationAdapterFactory.cs @@ -0,0 +1,12 @@ +using MQTTnet.Core.Client; +using MQTTnet.Core.Channel; + +namespace MQTTnet.Core.Adapter +{ + public interface IMqttCommunicationAdapterFactory + { + IMqttCommunicationAdapter CreateClientMqttCommunicationAdapter(IMqttClientOptions options); + + IMqttCommunicationAdapter CreateServerMqttCommunicationAdapter(IMqttCommunicationChannel channel); + } +} \ No newline at end of file diff --git a/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs b/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs index 2bed87d8..d47d4cee 100644 --- a/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs +++ b/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs @@ -4,23 +4,23 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Channel; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Exceptions; using MQTTnet.Core.Internal; using MQTTnet.Core.Packets; using MQTTnet.Core.Serializer; +using Microsoft.Extensions.Logging; namespace MQTTnet.Core.Adapter { public class MqttChannelCommunicationAdapter : IMqttCommunicationAdapter { - private readonly MqttNetTrace _trace; + private readonly ILogger _logger; private readonly IMqttCommunicationChannel _channel; private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); - public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer, MqttNetTrace trace) + public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer, ILogger logger) { - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _channel = channel ?? throw new ArgumentNullException(nameof(channel)); PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } @@ -96,7 +96,7 @@ namespace MQTTnet.Core.Adapter continue; } - _trace.Information(nameof(MqttChannelCommunicationAdapter), "TX >>> {0} [Timeout={1}]", packet, timeout); + _logger.LogInformation("TX >>> {0} [Timeout={1}]", packet, timeout); var writeBuffer = PacketSerializer.Serialize(packet); await _channel.SendStream.WriteAsync(writeBuffer, 0, writeBuffer.Length, cancellationToken).ConfigureAwait(false); @@ -162,7 +162,7 @@ namespace MQTTnet.Core.Adapter throw new MqttProtocolViolationException("Received malformed packet."); } - _trace.Information(nameof(MqttChannelCommunicationAdapter), "RX <<< {0}", packet); + _logger.LogInformation("RX <<< {0}", packet); return packet; } catch (TaskCanceledException) diff --git a/MQTTnet.Core/Client/IMqttClientFactory.cs b/MQTTnet.Core/Client/IMqttClientFactory.cs index e63047a8..7547906e 100644 --- a/MQTTnet.Core/Client/IMqttClientFactory.cs +++ b/MQTTnet.Core/Client/IMqttClientFactory.cs @@ -1,12 +1,11 @@ -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.ManagedClient; +using MQTTnet.Core.ManagedClient; namespace MQTTnet.Core.Client { public interface IMqttClientFactory { - IMqttClient CreateMqttClient(IMqttNetTraceHandler traceHandler = null); + IMqttClient CreateMqttClient(); - ManagedMqttClient CreateManagedMqttClient(IMqttNetTraceHandler traceHandler = null); + ManagedMqttClient CreateManagedMqttClient(); } } \ No newline at end of file diff --git a/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs b/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs deleted file mode 100644 index 0e295900..00000000 --- a/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MQTTnet.Core.Adapter; - -namespace MQTTnet.Core.Client -{ - public interface IMqttCommunicationAdapterFactory - { - IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options); - } -} \ No newline at end of file diff --git a/MQTTnet.Core/Client/MqttClient.cs b/MQTTnet.Core/Client/MqttClient.cs index fe1e4aba..67ffb126 100644 --- a/MQTTnet.Core/Client/MqttClient.cs +++ b/MQTTnet.Core/Client/MqttClient.cs @@ -4,11 +4,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Exceptions; using MQTTnet.Core.Internal; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; +using Microsoft.Extensions.Logging; namespace MQTTnet.Core.Client { @@ -16,21 +16,21 @@ namespace MQTTnet.Core.Client { private readonly HashSet _unacknowledgedPublishPackets = new HashSet(); private readonly MqttPacketDispatcher _packetDispatcher; - private readonly IMqttCommunicationAdapterFactory _communicationChannelFactory; - private readonly MqttNetTrace _trace; + private readonly IMqttCommunicationAdapterFactory _communicationAdapterFactory; + private readonly ILogger _logger; private IMqttClientOptions _options; private bool _isReceivingPackets; private int _latestPacketIdentifier; private CancellationTokenSource _cancellationTokenSource; private IMqttCommunicationAdapter _adapter; + private IDisposable _scopeHandle; - public MqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory, MqttNetTrace trace) + public MqttClient(IMqttCommunicationAdapterFactory communicationAdapterFactory, ILogger logger, MqttPacketDispatcher packetDispatcher) { - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); - _communicationChannelFactory = communicationChannelFactory ?? throw new ArgumentNullException(nameof(communicationChannelFactory)); - - _packetDispatcher = new MqttPacketDispatcher(trace); + _communicationAdapterFactory = communicationAdapterFactory ?? throw new ArgumentNullException(nameof(communicationAdapterFactory)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _packetDispatcher = packetDispatcher ?? throw new ArgumentNullException(nameof(packetDispatcher)); } public event EventHandler Connected; @@ -52,16 +52,17 @@ namespace MQTTnet.Core.Client _latestPacketIdentifier = 0; _packetDispatcher.Reset(); - _adapter = _communicationChannelFactory.CreateMqttCommunicationAdapter(options); + _adapter = _communicationAdapterFactory.CreateClientMqttCommunicationAdapter(options); - _trace.Verbose(nameof(MqttClient), "Trying to connect with server."); + _scopeHandle = _logger.BeginScope(options.ClientId); + _logger.LogTrace("Trying to connect with server."); await _adapter.ConnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); - _trace.Verbose(nameof(MqttClient), "Connection with server established."); + _logger.LogTrace("Connection with server established."); await SetupIncomingPacketProcessingAsync(); var connectResponse = await AuthenticateAsync(options.WillMessage); - _trace.Verbose(nameof(MqttClient), "MQTT connection with server established."); + _logger.LogTrace("MQTT connection with server established."); if (_options.KeepAlivePeriod != TimeSpan.Zero) { @@ -93,6 +94,7 @@ namespace MQTTnet.Core.Client finally { await DisconnectInternalAsync().ConfigureAwait(false); + _scopeHandle.Dispose(); } } @@ -246,11 +248,11 @@ namespace MQTTnet.Core.Client try { await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); - _trace.Information(nameof(MqttClient), "Disconnected from adapter."); + _logger.LogInformation("Disconnected from adapter."); } catch (Exception exception) { - _trace.Warning(nameof(MqttClient), exception, "Error while disconnecting from adapter."); + _logger.LogWarning(new EventId(), exception, "Error while disconnecting from adapter."); } finally { @@ -262,7 +264,7 @@ namespace MQTTnet.Core.Client { try { - _trace.Information(nameof(MqttClient), "Received <<< {0}", packet); + _logger.LogInformation("Received <<< {0}", packet); if (packet is MqttPingReqPacket) { @@ -292,7 +294,7 @@ namespace MQTTnet.Core.Client } catch (Exception exception) { - _trace.Error(nameof(MqttClient), exception, "Unhandled exception while processing received packet."); + _logger.LogError(new EventId(), exception, "Unhandled exception while processing received packet."); } } @@ -305,7 +307,7 @@ namespace MQTTnet.Core.Client } catch (Exception exception) { - _trace.Error(nameof(MqttClient), exception, "Unhandled exception while handling application message."); + _logger.LogError(new EventId(), exception, "Unhandled exception while handling application message."); } } @@ -369,7 +371,7 @@ namespace MQTTnet.Core.Client private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken) { - _trace.Information(nameof(MqttClient), "Start sending keep alive packets."); + _logger.LogInformation("Start sending keep alive packets."); try { @@ -394,23 +396,23 @@ namespace MQTTnet.Core.Client return; } - _trace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending/receiving keep alive packets."); + _logger.LogWarning(new EventId(), exception, "MQTT communication exception while sending/receiving keep alive packets."); await DisconnectInternalAsync().ConfigureAwait(false); } catch (Exception exception) { - _trace.Warning(nameof(MqttClient), exception, "Unhandled exception while sending/receiving keep alive packets."); + _logger.LogWarning(new EventId(), exception, "Unhandled exception while sending/receiving keep alive packets."); await DisconnectInternalAsync().ConfigureAwait(false); } finally { - _trace.Information(nameof(MqttClient), "Stopped sending keep alive packets."); + _logger.LogInformation("Stopped sending keep alive packets."); } } private async Task ReceivePackets(CancellationToken cancellationToken) { - _trace.Information(nameof(MqttClient), "Start receiving packets."); + _logger.LogInformation("Start receiving packets."); try { @@ -437,17 +439,17 @@ namespace MQTTnet.Core.Client return; } - _trace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets."); + _logger.LogWarning(new EventId(), exception, "MQTT communication exception while receiving packets."); await DisconnectInternalAsync().ConfigureAwait(false); } catch (Exception exception) { - _trace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets."); + _logger.LogError(new EventId(), exception, "Unhandled exception while receiving packets."); await DisconnectInternalAsync().ConfigureAwait(false); } finally { - _trace.Information(nameof(MqttClient), "Stopped receiving packets."); + _logger.LogInformation(nameof(MqttClient), "Stopped receiving packets."); } } diff --git a/MQTTnet.Core/Client/MqttPacketDispatcher.cs b/MQTTnet.Core/Client/MqttPacketDispatcher.cs index 40902dbc..a999d697 100644 --- a/MQTTnet.Core/Client/MqttPacketDispatcher.cs +++ b/MQTTnet.Core/Client/MqttPacketDispatcher.cs @@ -1,10 +1,10 @@ using System; +using System.Collections.Concurrent; using System.Threading.Tasks; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Exceptions; using MQTTnet.Core.Internal; using MQTTnet.Core.Packets; -using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; namespace MQTTnet.Core.Client { @@ -12,11 +12,11 @@ namespace MQTTnet.Core.Client { private readonly ConcurrentDictionary> _packetByResponseType = new ConcurrentDictionary>(); private readonly ConcurrentDictionary>> _packetByResponseTypeAndIdentifier = new ConcurrentDictionary>>(); - private readonly MqttNetTrace _trace; + private readonly ILogger _logger; - public MqttPacketDispatcher(MqttNetTrace trace) + public MqttPacketDispatcher(ILogger logger) { - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task WaitForPacketAsync(MqttBasePacket request, Type responseType, TimeSpan timeout) @@ -30,7 +30,7 @@ namespace MQTTnet.Core.Client } catch (MqttCommunicationTimedOutException) { - _trace.Warning(nameof(MqttPacketDispatcher), "Timeout while waiting for packet of type '{0}'.", responseType.Name); + _logger.LogWarning("Timeout while waiting for packet of type '{0}'.", responseType.Name); throw; } finally diff --git a/MQTTnet.Core/Diagnostics/IMqttNetTraceHandler.cs b/MQTTnet.Core/Diagnostics/IMqttNetTraceHandler.cs deleted file mode 100644 index 969cce08..00000000 --- a/MQTTnet.Core/Diagnostics/IMqttNetTraceHandler.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MQTTnet.Core.Diagnostics -{ - public interface IMqttNetTraceHandler - { - bool IsEnabled { get; } - - void HandleTraceMessage(MqttNetTraceMessage traceMessage); - } -} diff --git a/MQTTnet.Core/Diagnostics/MqttNetTrace.cs b/MQTTnet.Core/Diagnostics/MqttNetTrace.cs deleted file mode 100644 index ece6cf75..00000000 --- a/MQTTnet.Core/Diagnostics/MqttNetTrace.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; - -namespace MQTTnet.Core.Diagnostics -{ - public sealed class MqttNetTrace : IMqttNetTraceHandler - { - private readonly IMqttNetTraceHandler _traceHandler; - - public MqttNetTrace(IMqttNetTraceHandler customTraceHandler = null) - { - _traceHandler = customTraceHandler ?? this; - } - - public static event EventHandler TraceMessagePublished; - - public bool IsEnabled => TraceMessagePublished != null; - - public void Verbose(string source, string message, params object[] parameters) - { - Publish(source, MqttNetTraceLevel.Verbose, null, message, parameters); - } - - public void Information(string source, string message, params object[] parameters) - { - Publish(source, MqttNetTraceLevel.Information, null, message, parameters); - } - - public void Warning(string source, string message, params object[] parameters) - { - Publish(source, MqttNetTraceLevel.Warning, null, message, parameters); - } - - public void Warning(string source, Exception exception, string message, params object[] parameters) - { - Publish(source, MqttNetTraceLevel.Warning, exception, message, parameters); - } - - public void Error(string source, string message, params object[] parameters) - { - Publish(source, MqttNetTraceLevel.Error, null, message, parameters); - } - - public void Error(string source, Exception exception, string message, params object[] parameters) - { - Publish(source, MqttNetTraceLevel.Error, exception, message, parameters); - } - - public void HandleTraceMessage(MqttNetTraceMessage mqttNetTraceMessage) - { - TraceMessagePublished?.Invoke(this, new MqttNetTraceMessagePublishedEventArgs(mqttNetTraceMessage)); - } - - private void Publish(string source, MqttNetTraceLevel traceLevel, Exception exception, string message, params object[] parameters) - { - if (!_traceHandler.IsEnabled) - { - return; - } - - var now = DateTime.Now; - if (parameters?.Length > 0) - { - try - { - message = string.Format(message, parameters); - } - catch (Exception formatException) - { - Error(nameof(MqttNetTrace), formatException, "Error while tracing message: " + message); - return; - } - } - - _traceHandler.HandleTraceMessage(new MqttNetTraceMessage(now, Environment.CurrentManagedThreadId, source, traceLevel, message, exception)); - } - } -} diff --git a/MQTTnet.Core/Diagnostics/MqttNetTraceLevel.cs b/MQTTnet.Core/Diagnostics/MqttNetTraceLevel.cs deleted file mode 100644 index 5f3a13f4..00000000 --- a/MQTTnet.Core/Diagnostics/MqttNetTraceLevel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MQTTnet.Core.Diagnostics -{ - public enum MqttNetTraceLevel - { - Verbose, - Information, - Warning, - Error - } -} diff --git a/MQTTnet.Core/Diagnostics/MqttNetTraceMessage.cs b/MQTTnet.Core/Diagnostics/MqttNetTraceMessage.cs deleted file mode 100644 index 3eb1b568..00000000 --- a/MQTTnet.Core/Diagnostics/MqttNetTraceMessage.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace MQTTnet.Core.Diagnostics -{ - public sealed class MqttNetTraceMessage - { - public MqttNetTraceMessage(DateTime timestamp, int threadId, string source, MqttNetTraceLevel level, string message, Exception exception) - { - Timestamp = timestamp; - ThreadId = threadId; - Source = source; - Level = level; - Message = message; - Exception = exception; - } - - public DateTime Timestamp { get; } - - public int ThreadId { get; } - - public string Source { get; } - - public MqttNetTraceLevel Level { get; } - - public string Message { get; } - - public Exception Exception { get; } - } -} diff --git a/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs b/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs deleted file mode 100644 index 5b8f796d..00000000 --- a/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace MQTTnet.Core.Diagnostics -{ - public sealed class MqttNetTraceMessagePublishedEventArgs : EventArgs - { - public MqttNetTraceMessagePublishedEventArgs(MqttNetTraceMessage traceMessage) - { - TraceMessage = traceMessage ?? throw new ArgumentNullException(nameof(traceMessage)); - } - - public MqttNetTraceMessage TraceMessage { get; } - } -} diff --git a/MQTTnet.Core/MQTTnet.Core.csproj b/MQTTnet.Core/MQTTnet.Core.csproj index 2f55ab5d..3e877421 100644 --- a/MQTTnet.Core/MQTTnet.Core.csproj +++ b/MQTTnet.Core/MQTTnet.Core.csproj @@ -22,4 +22,9 @@ + + + + + \ No newline at end of file diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs index 06120780..6b2e1dd0 100644 --- a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs +++ b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs @@ -5,10 +5,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Exceptions; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; +using Microsoft.Extensions.Logging; namespace MQTTnet.Core.ManagedClient { @@ -19,7 +19,7 @@ namespace MQTTnet.Core.ManagedClient private readonly HashSet _subscriptions = new HashSet(); private readonly IMqttClient _mqttClient; - private readonly MqttNetTrace _trace; + private readonly ILogger _logger; private CancellationTokenSource _connectionCancellationToken; private CancellationTokenSource _publishingCancellationToken; @@ -27,12 +27,11 @@ namespace MQTTnet.Core.ManagedClient private IManagedMqttClientOptions _options; private bool _subscriptionsNotPushed; - public ManagedMqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory, MqttNetTrace trace) + public ManagedMqttClient(ILogger logger, IMqttClient mqttClient) { - if (communicationChannelFactory == null) throw new ArgumentNullException(nameof(communicationChannelFactory)); - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); - _mqttClient = new MqttClient(communicationChannelFactory, _trace); _mqttClient.Connected += OnConnected; _mqttClient.Disconnected += OnDisconnected; _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; @@ -77,7 +76,7 @@ namespace MQTTnet.Core.ManagedClient Task.Factory.StartNew(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - _trace.Information(nameof(ManagedMqttClient), "Started"); + _logger.LogInformation("Started"); } public Task StopAsync() @@ -179,16 +178,16 @@ namespace MQTTnet.Core.ManagedClient } catch (MqttCommunicationException exception) { - _trace.Warning(nameof(ManagedMqttClient), exception, "Communication exception while maintaining connection."); + _logger.LogWarning(new EventId(), exception, "Communication exception while maintaining connection."); } catch (Exception exception) { - _trace.Error(nameof(ManagedMqttClient), exception, "Unhandled exception while maintaining connection."); + _logger.LogError(new EventId(), exception, "Unhandled exception while maintaining connection."); } finally { await _mqttClient.DisconnectAsync().ConfigureAwait(false); - _trace.Information(nameof(ManagedMqttClient), "Stopped"); + _logger.LogInformation("Stopped"); } } @@ -217,7 +216,7 @@ namespace MQTTnet.Core.ManagedClient } finally { - _trace.Information(nameof(ManagedMqttClient), "Stopped publishing messages"); + _logger.LogInformation("Stopped publishing messages"); } } @@ -229,7 +228,7 @@ namespace MQTTnet.Core.ManagedClient } catch (MqttCommunicationException exception) { - _trace.Warning(nameof(ManagedMqttClient), exception, "Publishing application message failed."); + _logger.LogWarning(new EventId(), exception, "Publishing application message failed."); if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { @@ -238,13 +237,13 @@ namespace MQTTnet.Core.ManagedClient } catch (Exception exception) { - _trace.Error(nameof(ManagedMqttClient), exception, "Unhandled exception while publishing queued application message."); + _logger.LogError(new EventId(), exception, "Unhandled exception while publishing queued application message."); } } private async Task PushSubscriptionsAsync() { - _trace.Information(nameof(ManagedMqttClient), "Synchronizing subscriptions"); + _logger.LogInformation(nameof(ManagedMqttClient), "Synchronizing subscriptions"); List subscriptions; lock (_subscriptions) @@ -264,7 +263,7 @@ namespace MQTTnet.Core.ManagedClient } catch (Exception exception) { - _trace.Warning(nameof(ManagedMqttClient), exception, "Synchronizing subscriptions failed"); + _logger.LogWarning(new EventId(), exception, "Synchronizing subscriptions failed"); _subscriptionsNotPushed = true; } } diff --git a/MQTTnet.Core/Server/IMqttClientSesssionFactory.cs b/MQTTnet.Core/Server/IMqttClientSesssionFactory.cs new file mode 100644 index 00000000..f58dd5e7 --- /dev/null +++ b/MQTTnet.Core/Server/IMqttClientSesssionFactory.cs @@ -0,0 +1,7 @@ +namespace MQTTnet.Core.Server +{ + public interface IMqttClientSesssionFactory + { + MqttClientSession CreateClientSession(string sessionId, MqttClientSessionsManager mqttClientSessionsManager); + } +} diff --git a/MQTTnet.Core/Server/IMqttServerFactory.cs b/MQTTnet.Core/Server/IMqttServerFactory.cs index 865080b3..61d022f3 100644 --- a/MQTTnet.Core/Server/IMqttServerFactory.cs +++ b/MQTTnet.Core/Server/IMqttServerFactory.cs @@ -1,9 +1,7 @@ -using MQTTnet.Core.Diagnostics; - -namespace MQTTnet.Core.Server +namespace MQTTnet.Core.Server { public interface IMqttServerFactory { - IMqttServer CreateMqttServer(MqttServerOptions options, IMqttNetTraceHandler traceHandler = null); + IMqttServer CreateMqttServer(); } } \ No newline at end of file diff --git a/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs b/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs index 551dc8e7..6b3f0233 100644 --- a/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs +++ b/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs @@ -3,10 +3,10 @@ using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Exceptions; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; +using Microsoft.Extensions.Logging; namespace MQTTnet.Core.Server { @@ -15,11 +15,11 @@ namespace MQTTnet.Core.Server private readonly BlockingCollection _pendingPublishPackets = new BlockingCollection(); private readonly MqttClientSession _session; private readonly MqttServerOptions _options; - private readonly MqttNetTrace _trace; + private readonly ILogger _logger; - public MqttClientPendingMessagesQueue(MqttServerOptions options, MqttClientSession session, MqttNetTrace trace) + public MqttClientPendingMessagesQueue(MqttServerOptions options, MqttClientSession session, ILogger logger) { - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _session = session ?? throw new ArgumentNullException(nameof(session)); _options = options ?? throw new ArgumentNullException(nameof(options)); } @@ -52,7 +52,7 @@ namespace MQTTnet.Core.Server } catch (Exception exception) { - _trace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Unhandled exception while sending pending publish packets."); + _logger.LogError(new EventId(), exception, "Unhandled exception while sending pending publish packets."); } } @@ -68,18 +68,18 @@ namespace MQTTnet.Core.Server { if (exception is MqttCommunicationTimedOutException) { - _trace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to timeout."); + _logger.LogWarning(new EventId(), exception, "Sending publish packet failed due to timeout."); } else if (exception is MqttCommunicationException) { - _trace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to communication exception."); + _logger.LogWarning(new EventId(), exception, "Sending publish packet failed due to communication exception."); } if (exception is OperationCanceledException) { } else { - _trace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed."); + _logger.LogError(new EventId(), exception, "Sending publish packet failed."); } if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) diff --git a/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs b/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs index 22de3054..68a7b99b 100644 --- a/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs +++ b/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs @@ -2,21 +2,22 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Packets; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace MQTTnet.Core.Server { public sealed class MqttClientRetainedMessagesManager { private readonly Dictionary _retainedMessages = new Dictionary(); - private readonly MqttNetTrace _trace; + private readonly ILogger _logger; private readonly MqttServerOptions _options; - public MqttClientRetainedMessagesManager(MqttServerOptions options, MqttNetTrace trace) + public MqttClientRetainedMessagesManager(IOptions options, ILogger logger) { - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); - _options = options ?? throw new ArgumentNullException(nameof(options)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _options = options.Value ?? throw new ArgumentNullException(nameof(options)); } public async Task LoadMessagesAsync() @@ -40,7 +41,7 @@ namespace MQTTnet.Core.Server } catch (Exception exception) { - _trace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while loading retained messages."); + _logger.LogError(new EventId(), exception, "Unhandled exception while loading retained messages."); } } @@ -54,12 +55,12 @@ namespace MQTTnet.Core.Server if (applicationMessage.Payload?.Any() == false) { _retainedMessages.Remove(applicationMessage.Topic); - _trace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); + _logger.LogInformation("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); } else { _retainedMessages[applicationMessage.Topic] = applicationMessage; - _trace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' updated retained message for topic '{1}'.", clientId, applicationMessage.Topic); + _logger.LogInformation("Client '{0}' updated retained message for topic '{1}'.", clientId, applicationMessage.Topic); } allRetainedMessages = new List(_retainedMessages.Values); @@ -75,7 +76,7 @@ namespace MQTTnet.Core.Server } catch (Exception exception) { - _trace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while saving retained messages."); + _logger.LogError(new EventId(), exception, "Unhandled exception while saving retained messages."); } } diff --git a/MQTTnet.Core/Server/MqttClientSession.cs b/MQTTnet.Core/Server/MqttClientSession.cs index f913901e..b519ef28 100644 --- a/MQTTnet.Core/Server/MqttClientSession.cs +++ b/MQTTnet.Core/Server/MqttClientSession.cs @@ -3,12 +3,12 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Exceptions; using MQTTnet.Core.Internal; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; using MQTTnet.Core.Serializer; +using Microsoft.Extensions.Logging; namespace MQTTnet.Core.Server { @@ -20,19 +20,20 @@ namespace MQTTnet.Core.Server private readonly MqttClientSessionsManager _mqttClientSessionsManager; private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue; private readonly MqttServerOptions _options; - private readonly MqttNetTrace _trace; + private readonly ILogger _logger; private IMqttCommunicationAdapter _adapter; private CancellationTokenSource _cancellationTokenSource; private MqttApplicationMessage _willMessage; - public MqttClientSession(string clientId, MqttServerOptions options, MqttClientSessionsManager mqttClientSessionsManager, MqttNetTrace trace) + public MqttClientSession(string clientId, MqttClientSessionsManager mqttClientSessionsManager, ILogger logger, ILogger msgQueueLogger) { - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); ClientId = clientId; - _options = options ?? throw new ArgumentNullException(nameof(options)); + _options = mqttClientSessionsManager.Options; _mqttClientSessionsManager = mqttClientSessionsManager ?? throw new ArgumentNullException(nameof(mqttClientSessionsManager)); - _pendingMessagesQueue = new MqttClientPendingMessagesQueue(options, this, trace); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + _pendingMessagesQueue = new MqttClientPendingMessagesQueue(mqttClientSessionsManager.Options, this, msgQueueLogger); } public string ClientId { get; } @@ -60,11 +61,11 @@ namespace MQTTnet.Core.Server } catch (MqttCommunicationException exception) { - _trace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", ClientId); + _logger.LogWarning(new EventId(), exception, "Client '{0}': Communication exception while processing client packets.", ClientId); } catch (Exception exception) { - _trace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); + _logger.LogError(new EventId(), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); } } @@ -81,7 +82,7 @@ namespace MQTTnet.Core.Server _adapter = null; - _trace.Information(nameof(MqttClientSession), "Client '{0}': Disconnected.", ClientId); + _logger.LogInformation("Client '{0}': Disconnected.", ClientId); } public void EnqueuePublishPacket(MqttPublishPacket publishPacket) @@ -94,7 +95,7 @@ namespace MQTTnet.Core.Server } _pendingMessagesQueue.Enqueue(publishPacket); - _trace.Verbose(nameof(MqttClientSession), "Client '{0}': Enqueued pending publish packet.", ClientId); + _logger.LogTrace("Client '{0}': Enqueued pending publish packet.", ClientId); } public void Dispose() @@ -118,12 +119,12 @@ namespace MQTTnet.Core.Server } catch (MqttCommunicationException exception) { - _trace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", ClientId); + _logger.LogWarning(new EventId(), exception, "Client '{0}': Communication exception while processing client packets.", ClientId); Stop(); } catch (Exception exception) { - _trace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); + _logger.LogError(new EventId(), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); Stop(); } } @@ -165,7 +166,7 @@ namespace MQTTnet.Core.Server } else { - _trace.Warning(nameof(MqttClientSession), "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); + _logger.LogWarning("Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); Stop(); } } diff --git a/MQTTnet.Core/Server/MqttClientSessionsManager.cs b/MQTTnet.Core/Server/MqttClientSessionsManager.cs index aedd3d76..f1840eb4 100644 --- a/MQTTnet.Core/Server/MqttClientSessionsManager.cs +++ b/MQTTnet.Core/Server/MqttClientSessionsManager.cs @@ -4,26 +4,28 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Exceptions; using MQTTnet.Core.Internal; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; using MQTTnet.Core.Serializer; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace MQTTnet.Core.Server { public sealed class MqttClientSessionsManager { private readonly Dictionary _clientSessions = new Dictionary(); - private readonly MqttServerOptions _options; - private readonly MqttNetTrace _trace; + private readonly ILogger _logger; + private readonly IMqttClientSesssionFactory _mqttClientSesssionFactory; - public MqttClientSessionsManager(MqttServerOptions options, MqttNetTrace trace) + public MqttClientSessionsManager(IOptions options, ILogger logger, MqttClientRetainedMessagesManager retainedMessagesManager, IMqttClientSesssionFactory mqttClientSesssionFactory) { - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); - _options = options ?? throw new ArgumentNullException(nameof(options)); - RetainedMessagesManager = new MqttClientRetainedMessagesManager(options, trace); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + Options = options.Value ?? throw new ArgumentNullException(nameof(options)); + RetainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(options)); + _mqttClientSesssionFactory = mqttClientSesssionFactory ?? throw new ArgumentNullException(nameof(mqttClientSesssionFactory)); } public event EventHandler ApplicationMessageReceived; @@ -31,13 +33,14 @@ namespace MQTTnet.Core.Server public event EventHandler ClientDisconnected; public MqttClientRetainedMessagesManager RetainedMessagesManager { get; } + public MqttServerOptions Options { get; } public async Task RunClientSessionAsync(IMqttCommunicationAdapter clientAdapter) { var clientId = string.Empty; try { - if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false) is MqttConnectPacket connectPacket)) + if (!(await clientAdapter.ReceivePacketAsync(Options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false) is MqttConnectPacket connectPacket)) { throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); } @@ -50,7 +53,7 @@ namespace MQTTnet.Core.Server var connectReturnCode = ValidateConnection(connectPacket); if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { - await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket + await clientAdapter.SendPacketsAsync(Options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket { ConnectReturnCode = connectReturnCode }).ConfigureAwait(false); @@ -60,7 +63,7 @@ namespace MQTTnet.Core.Server var clientSession = GetOrCreateClientSession(connectPacket); - await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket + await clientAdapter.SendPacketsAsync(Options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket { ConnectReturnCode = connectReturnCode, IsSessionPresent = clientSession.IsExistingSession @@ -72,17 +75,20 @@ namespace MQTTnet.Core.Server ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion })); - await clientSession.Session.RunAsync(connectPacket.WillMessage, clientAdapter).ConfigureAwait(false); + using (_logger.BeginScope(clientId)) + { + await clientSession.Session.RunAsync(connectPacket.WillMessage, clientAdapter).ConfigureAwait(false); + } } catch (Exception exception) { - _trace.Error(nameof(MqttServer), exception, exception.Message); + _logger.LogError(new EventId(), exception, exception.Message); } finally { try { - await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); + await clientAdapter.DisconnectAsync(Options.DefaultCommunicationTimeout).ConfigureAwait(false); } catch (Exception) { @@ -126,7 +132,7 @@ namespace MQTTnet.Core.Server } catch (Exception exception) { - _trace.Error(nameof(MqttClientSessionsManager), exception, "Error while processing application message"); + _logger.LogError(new EventId(), exception, "Error while processing application message"); } lock (_clientSessions) @@ -140,9 +146,9 @@ namespace MQTTnet.Core.Server private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) { - if (_options.ConnectionValidator != null) + if (Options.ConnectionValidator != null) { - return _options.ConnectionValidator(connectPacket); + return Options.ConnectionValidator(connectPacket); } return MqttConnectReturnCode.ConnectionAccepted; @@ -160,11 +166,11 @@ namespace MQTTnet.Core.Server _clientSessions.Remove(connectPacket.ClientId); clientSession.Dispose(); clientSession = null; - _trace.Verbose(nameof(MqttClientSessionsManager), "Disposed existing session of client '{0}'.", connectPacket.ClientId); + _logger.LogTrace("Disposed existing session of client '{0}'.", connectPacket.ClientId); } else { - _trace.Verbose(nameof(MqttClientSessionsManager), "Reusing existing session of client '{0}'.", connectPacket.ClientId); + _logger.LogTrace("Reusing existing session of client '{0}'.", connectPacket.ClientId); } } @@ -173,10 +179,10 @@ namespace MQTTnet.Core.Server { isExistingSession = false; - clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _trace); + clientSession = _mqttClientSesssionFactory.CreateClientSession(connectPacket.ClientId, this); _clientSessions[connectPacket.ClientId] = clientSession; - _trace.Verbose(nameof(MqttClientSessionsManager), "Created a new session for client '{0}'.", connectPacket.ClientId); + _logger.LogTrace("Created a new session for client '{0}'.", connectPacket.ClientId); } return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession }; diff --git a/MQTTnet.Core/Server/MqttServer.cs b/MQTTnet.Core/Server/MqttServer.cs index 341b3701..b5c25de7 100644 --- a/MQTTnet.Core/Server/MqttServer.cs +++ b/MQTTnet.Core/Server/MqttServer.cs @@ -3,26 +3,33 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Linq; namespace MQTTnet.Core.Server { public sealed class MqttServer : IMqttServer { - private readonly MqttNetTrace _trace; + private readonly ILogger _logger; private readonly MqttClientSessionsManager _clientSessionsManager; private readonly ICollection _adapters; private readonly MqttServerOptions _options; private CancellationTokenSource _cancellationTokenSource; - public MqttServer(MqttServerOptions options, ICollection adapters, MqttNetTrace trace) + public MqttServer(IOptions options, IEnumerable adapters, ILogger logger, MqttClientSessionsManager clientSessionsManager) { - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); - _options = options ?? throw new ArgumentNullException(nameof(options)); - _adapters = adapters ?? throw new ArgumentNullException(nameof(adapters)); + _options = options.Value ?? throw new ArgumentNullException(nameof(options)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager)); + + if (adapters == null) + { + throw new ArgumentNullException(nameof(adapters)); + } + _adapters = adapters.ToList(); - _clientSessionsManager = new MqttClientSessionsManager(options, trace); _clientSessionsManager.ApplicationMessageReceived += (s, e) => ApplicationMessageReceived?.Invoke(s, e); _clientSessionsManager.ClientConnected += OnClientConnected; _clientSessionsManager.ClientDisconnected += OnClientDisconnected; @@ -59,7 +66,7 @@ namespace MQTTnet.Core.Server await adapter.StartAsync(_options); } - _trace.Information(nameof(MqttServer), "Started."); + _logger.LogInformation("Started."); } public async Task StopAsync() @@ -76,7 +83,7 @@ namespace MQTTnet.Core.Server _clientSessionsManager.Clear(); - _trace.Information(nameof(MqttServer), "Stopped."); + _logger.LogInformation("Stopped."); } private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) @@ -86,13 +93,13 @@ namespace MQTTnet.Core.Server private void OnClientConnected(object sender, MqttClientConnectedEventArgs eventArgs) { - _trace.Information(nameof(MqttServer), "Client '{0}': Connected.", eventArgs.Client.ClientId); + _logger.LogInformation("Client '{0}': Connected.", eventArgs.Client.ClientId); ClientConnected?.Invoke(this, eventArgs); } private void OnClientDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs) { - _trace.Information(nameof(MqttServer), "Client '{0}': Disconnected.", eventArgs.Client.ClientId); + _logger.LogInformation("Client '{0}': Disconnected.", eventArgs.Client.ClientId); ClientDisconnected?.Invoke(this, eventArgs); } } diff --git a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj index 2a033595..7ba48954 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj +++ b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj @@ -7,6 +7,7 @@ + diff --git a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs index bed32182..7e3a7dca 100644 --- a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs +++ b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs @@ -1,4 +1,5 @@ using MQTTnet.Core.Adapter; +using MQTTnet.Core.Channel; using MQTTnet.Core.Client; namespace MQTTnet.Core.Tests @@ -12,7 +13,12 @@ namespace MQTTnet.Core.Tests _adapter = adapter; } - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options) + public IMqttCommunicationAdapter CreateClientMqttCommunicationAdapter(IMqttClientOptions options) + { + return _adapter; + } + + public IMqttCommunicationAdapter CreateServerMqttCommunicationAdapter(IMqttCommunicationChannel channel) { return _adapter; } diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs index 948497ee..e866ca82 100644 --- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs @@ -5,10 +5,10 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Core.Adapter; using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; using MQTTnet.Core.Server; +using Microsoft.Extensions.DependencyInjection; namespace MQTTnet.Core.Tests { @@ -52,7 +52,12 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_WillMessage() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); + var services = new ServiceCollection() + .AddMqttServer() + .AddSingleton(serverAdapter) + .BuildServiceProvider(); + + var s = services.GetRequiredService(); await s.StartAsync(); var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build(); @@ -76,7 +81,12 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_Unsubscribe() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); + var services = new ServiceCollection() + .AddMqttServer() + .AddSingleton(serverAdapter) + .BuildServiceProvider(); + + var s = services.GetRequiredService(); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -112,7 +122,12 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_Publish() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); + var services = new ServiceCollection() + .AddMqttServer() + .AddSingleton(serverAdapter) + .BuildServiceProvider(); + + var s = services.GetRequiredService(); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -135,7 +150,12 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_NoRetainedMessage() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); + var services = new ServiceCollection() + .AddMqttServer() + .AddSingleton(serverAdapter) + .BuildServiceProvider(); + + var s = services.GetRequiredService(); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -158,7 +178,12 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_RetainedMessage() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); + var services = new ServiceCollection() + .AddMqttServer() + .AddSingleton(serverAdapter) + .BuildServiceProvider(); + + var s = services.GetRequiredService(); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -181,7 +206,12 @@ namespace MQTTnet.Core.Tests public async Task MqttServer_ClearRetainedMessage() { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); + var services = new ServiceCollection() + .AddMqttServer() + .AddSingleton(serverAdapter) + .BuildServiceProvider(); + + var s = services.GetRequiredService(); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -207,7 +237,12 @@ namespace MQTTnet.Core.Tests var storage = new TestStorage(); var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions { Storage = storage }, new List { serverAdapter }, new MqttNetTrace()); + var services = new ServiceCollection() + .AddMqttServer() + .AddSingleton(serverAdapter) + .BuildServiceProvider(); + + var s = services.GetRequiredService(); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -216,7 +251,7 @@ namespace MQTTnet.Core.Tests await s.StopAsync(); - s = new MqttServer(new MqttServerOptions { Storage = storage }, new List { serverAdapter }, new MqttNetTrace()); + s = services.GetRequiredService(); await s.StartAsync(); var c2 = await serverAdapter.ConnectTestClient(s, "c2"); @@ -244,7 +279,12 @@ namespace MQTTnet.Core.Tests }; var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(options, new List { serverAdapter }, new MqttNetTrace()); + var services = new ServiceCollection() + .AddMqttServer() + .AddSingleton(serverAdapter) + .BuildServiceProvider(); + + var s = services.GetRequiredService(); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); @@ -290,7 +330,12 @@ namespace MQTTnet.Core.Tests int expectedReceivedMessagesCount) { var serverAdapter = new TestMqttServerAdapter(); - var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter }, new MqttNetTrace()); + var services = new ServiceCollection() + .AddMqttServer() + .AddSingleton(serverAdapter) + .BuildServiceProvider(); + + var s = services.GetRequiredService(); await s.StartAsync(); var c1 = await serverAdapter.ConnectTestClient(s, "c1"); diff --git a/Tests/MQTTnet.Core.Tests/ServiceCollectionTest.cs b/Tests/MQTTnet.Core.Tests/ServiceCollectionTest.cs new file mode 100644 index 00000000..0117ed08 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/ServiceCollectionTest.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace MQTTnet.Core.Tests +{ + [TestClass] + public class ServiceCollectionTest + { + [TestMethod] + public void TestCanConstructAllServices() + { + var services = new ServiceCollection() + .AddLogging() + .AddMqttServer() + .AddMqttClient(); + + var serviceProvider = services + .BuildServiceProvider(); + + foreach (var service in services) + { + if (service.ServiceType.IsGenericType) + { + continue; + } + serviceProvider.GetRequiredService(service.ServiceType); + } + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/TestClientSessionFactory.cs b/Tests/MQTTnet.Core.Tests/TestClientSessionFactory.cs new file mode 100644 index 00000000..519ef500 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/TestClientSessionFactory.cs @@ -0,0 +1,12 @@ +using MQTTnet.Core.Server; + +namespace MQTTnet.Core.Tests +{ + public class TestClientSessionFactory : IMqttClientSesssionFactory + { + public MqttClientSession CreateClientSession(string sessionId, MqttClientSessionsManager mqttClientSessionsManager) + { + return new MqttClientSession(sessionId, mqttClientSessionsManager, new TestLogger(), new TestLogger()); + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/TestLogger.cs b/Tests/MQTTnet.Core.Tests/TestLogger.cs new file mode 100644 index 00000000..ea597a6e --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/TestLogger.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Logging; +using System; + +namespace MQTTnet.Core.Tests +{ + public class TestLogger : ILogger + { + public IDisposable BeginScope(TState state) + { + throw new NotImplementedException(); + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs index ae15cc25..0655a096 100644 --- a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using MQTTnet.Core.Adapter; using MQTTnet.Core.Server; using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; namespace MQTTnet.Core.Tests { @@ -18,7 +17,7 @@ namespace MQTTnet.Core.Tests adapterA.Partner = adapterB; adapterB.Partner = adapterA; - var client = new MqttClient(new MqttCommunicationAdapterFactory(adapterA), new MqttNetTrace()); + var client = new MqttClient(new MqttCommunicationAdapterFactory(adapterA), new TestLogger(), new MqttPacketDispatcher(new TestLogger())); var connected = WaitForClientToConnect(server, clientId); FireClientAcceptedEvent(adapterB); diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MqttWebSocketServerAdapter.cs b/Tests/MQTTnet.TestApp.AspNetCore2/MqttWebSocketServerAdapter.cs index c9f52933..90ad49f2 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/MqttWebSocketServerAdapter.cs +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MqttWebSocketServerAdapter.cs @@ -5,20 +5,22 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Adapter; using MQTTnet.Core.Channel; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Serializer; using MQTTnet.Core.Server; using MQTTnet.Implementations; +using Microsoft.Extensions.Logging; namespace MQTTnet.TestApp.AspNetCore2 { public class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable { - private readonly MqttNetTrace _trace; + private readonly ILogger _logger; + private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory; - public MqttWebSocketServerAdapter(MqttNetTrace trace) + public MqttWebSocketServerAdapter(ILogger logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory) { - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory)); } public event EventHandler ClientAccepted; @@ -38,7 +40,7 @@ namespace MQTTnet.TestApp.AspNetCore2 if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); var channel = new MqttWebSocketServerChannel(webSocket); - var clientAdapter = new MqttChannelCommunicationAdapter(channel, new MqttPacketSerializer(), _trace); + var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(channel); var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); ClientAccepted?.Invoke(this, eventArgs); diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs index d730d2cd..a4f5298f 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs @@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Server; namespace MQTTnet.TestApp.AspNetCore2 @@ -14,25 +13,18 @@ namespace MQTTnet.TestApp.AspNetCore2 { public void ConfigureServices(IServiceCollection services) { + services.AddMqttServer(); + services.AddSingleton(); + services.AddSingleton(); } public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(LogLevel.Debug); + loggerFactory.AddDebug(); - MqttNetTrace.TraceMessagePublished += (s, e) => - { - Debug.WriteLine($">> [{e.TraceMessage.Timestamp}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}"); - if (e.TraceMessage.Exception != null) - { - Debug.WriteLine(e.TraceMessage.Exception.Message); - } - }; - - var trace = new MqttNetTrace(); - var adapter = new MqttWebSocketServerAdapter(trace); - var options = new MqttServerOptions(); - var mqttServer = new MqttServer(options, new List { adapter }, new MqttNetTrace()); + var adapter = app.ApplicationServices.GetService(); + var mqttServer = app.ApplicationServices.GetService(); await mqttServer.StartAsync(); app.UseWebSockets(); diff --git a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj index 41ece1b4..4c0f99d2 100644 --- a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj +++ b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj @@ -3,10 +3,11 @@ Exe Full - netcoreapp2.0;net45 + netcoreapp2.0;net451 + @@ -15,4 +16,10 @@ + + + 1.1.2 + + + diff --git a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs index 4959f370..b54de068 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs @@ -2,10 +2,11 @@ using System.Threading.Tasks; using MQTTnet.Core; using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.ManagedClient; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace MQTTnet.TestApp.NetCore { @@ -13,14 +14,14 @@ namespace MQTTnet.TestApp.NetCore { public static async Task RunAsync() { - MqttNetTrace.TraceMessagePublished += (s, e) => - { - Console.WriteLine($">> [{e.TraceMessage.Timestamp:O}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}"); - if (e.TraceMessage.Exception != null) - { - Console.WriteLine(e.TraceMessage.Exception); - } - }; + var services = new ServiceCollection() + .AddMqttClient() + .AddLogging() + .BuildServiceProvider(); + + services.GetService() + .AddConsole(); + var options = new ManagedMqttClientOptions { @@ -35,7 +36,7 @@ namespace MQTTnet.TestApp.NetCore try { - var managedClient = new MqttClientFactory().CreateManagedMqttClient(); + var managedClient = services.GetRequiredService(); managedClient.ApplicationMessageReceived += (s, e) => { Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic); diff --git a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index 638bdc0a..746aa74e 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -1,4 +1,6 @@ -using MQTTnet.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MQTTnet.Core; using MQTTnet.Core.Client; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; @@ -17,33 +19,58 @@ namespace MQTTnet.TestApp.NetCore { public static async Task RunAsync() { - var server = Task.Factory.StartNew(RunServerAsync, TaskCreationOptions.LongRunning); - var client = Task.Factory.StartNew(() => RunClientAsync(2000, TimeSpan.FromMilliseconds(10)), TaskCreationOptions.LongRunning); + var services = new ServiceCollection() + .AddMqttServer(options => { + + options.ConnectionValidator = p => + { + if (p.ClientId == "SpecialClient") + { + if (p.Username != "USER" || p.Password != "PASS") + { + return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; + } + } + + return MqttConnectReturnCode.ConnectionAccepted; + }; + + options.DefaultCommunicationTimeout = TimeSpan.FromMinutes(10); + }) + .AddMqttClient() + .AddLogging() + .BuildServiceProvider(); + + services.GetService() + .AddConsole(minLevel: LogLevel.Warning, includeScopes: true); + + Console.WriteLine("Press 'c' for concurrent sends. Otherwise in one batch."); + var concurrent = Console.ReadKey(intercept: true).KeyChar == 'c'; + + var server = Task.Factory.StartNew(() => RunServerAsync(services), TaskCreationOptions.LongRunning); + var client = Task.Factory.StartNew(() => RunClientAsync(2000, TimeSpan.FromMilliseconds(10), services, concurrent), TaskCreationOptions.LongRunning); await Task.WhenAll(server, client).ConfigureAwait(false); } - private static Task RunClientsAsync(int msgChunkSize, TimeSpan interval) + private static Task RunClientsAsync(int msgChunkSize, TimeSpan interval, IServiceProvider serviceProvider, bool concurrent) { - return Task.WhenAll(Enumerable.Range(0, 3).Select(i => Task.Run(() => RunClientAsync(msgChunkSize, interval)))); + return Task.WhenAll(Enumerable.Range(0, 3).Select(i => Task.Run(() => RunClientAsync(msgChunkSize, interval, serviceProvider, concurrent)))); } - private static async Task RunClientAsync(int msgChunkSize, TimeSpan interval) + private static async Task RunClientAsync(int msgChunkSize, TimeSpan interval, IServiceProvider serviceProvider, bool concurrent) { try { var options = new MqttClientTcpOptions { Server = "localhost", - ClientId = "XYZ", + ClientId = "Client1", CleanSession = true, DefaultCommunicationTimeout = TimeSpan.FromMinutes(10) }; - var client = new MqttClientFactory().CreateMqttClient(); - client.ApplicationMessageReceived += (s, e) => - { - }; + var client = serviceProvider.GetRequiredService(); client.Connected += async (s, e) => { @@ -113,8 +140,7 @@ namespace MQTTnet.TestApp.NetCore .Select(i => CreateMessage()) .ToList(); - Console.WriteLine("Press 'c' for concurrent sends. Otherwise in one batch."); - if (Console.ReadKey().KeyChar == 'c') + if (concurrent) { //send concurrent (test for raceconditions) var sendTasks = msgs @@ -165,28 +191,11 @@ namespace MQTTnet.TestApp.NetCore return Task.Run(() => client.PublishAsync(applicationMessage)); } - private static async Task RunServerAsync() + private static async Task RunServerAsync(IServiceProvider serviceProvider) { try { - var options = new MqttServerOptions - { - ConnectionValidator = p => - { - if (p.ClientId == "SpecialClient") - { - if (p.Username != "USER" || p.Password != "PASS") - { - return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; - } - } - - return MqttConnectReturnCode.ConnectionAccepted; - }, - DefaultCommunicationTimeout = TimeSpan.FromMinutes(10) - }; - - var mqttServer = new MqttServerFactory().CreateMqttServer(options); + var mqttServer = serviceProvider.GetRequiredService(); var msgs = 0; var stopwatch = Stopwatch.StartNew(); mqttServer.ApplicationMessageReceived += (sender, args) => diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 63c4ee24..f0b28c80 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -1,6 +1,5 @@ using MQTTnet.Core; using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; using MQTTnet.Core.Server; @@ -12,6 +11,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace MQTTnet.TestApp.NetCore { @@ -24,7 +25,7 @@ namespace MQTTnet.TestApp.NetCore Console.WriteLine("2 = Start server"); Console.WriteLine("3 = Start performance test"); Console.WriteLine("4 = Start managed client"); - + var pressedKey = Console.ReadKey(true); if (pressedKey.KeyChar == '1') { @@ -48,17 +49,16 @@ namespace MQTTnet.TestApp.NetCore private static async Task RunClientAsync() { - MqttNetTrace.TraceMessagePublished += (s, e) => - { - Console.WriteLine($">> [{e.TraceMessage.Timestamp:O}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}"); - if (e.TraceMessage.Exception != null) - { - Console.WriteLine(e.TraceMessage.Exception); - } - }; - try { + var services = new ServiceCollection() + .AddMqttServer() + .AddLogging() + .BuildServiceProvider(); + + services.GetService() + .AddConsole(); + var options = new MqttClientWebSocketOptions { Uri = "localhost", @@ -66,7 +66,7 @@ namespace MQTTnet.TestApp.NetCore CleanSession = true }; - var client = new MqttClientFactory().CreateMqttClient(); + var client = services.GetRequiredService(); client.ApplicationMessageReceived += (s, e) => { Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); @@ -139,7 +139,12 @@ namespace MQTTnet.TestApp.NetCore private static async void WikiCode() { { - var client = new MqttClientFactory().CreateMqttClient(new CustomTraceHandler("Client 1")); + var serviceProvider = new ServiceCollection() + .AddMqttServer() + .AddLogging() + .BuildServiceProvider(); + + var client = serviceProvider.GetRequiredService(); var message = new MqttApplicationMessageBuilder() .WithTopic("MyTopic") @@ -159,28 +164,6 @@ namespace MQTTnet.TestApp.NetCore } } - public class CustomTraceHandler : IMqttNetTraceHandler - { - private readonly string _clientId; - - public CustomTraceHandler(string clientId) - { - _clientId = clientId; - } - - public bool IsEnabled { get; } = true; - - public void HandleTraceMessage(MqttNetTraceMessage traceMessage) - { - // Client ID is added to the trace message. - Console.WriteLine($">> [{_clientId}] [{traceMessage.Timestamp:O}] [{traceMessage.ThreadId}] [{traceMessage.Source}] [{traceMessage.Level}]: {traceMessage.Message}"); - if (traceMessage.Exception != null) - { - Console.WriteLine(traceMessage.Exception); - } - } - } - public class RetainedMessageHandler : IMqttServerStorage { private const string Filename = "C:\\MQTT\\RetainedMessages.json"; diff --git a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs index 30d9596d..0432a671 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs @@ -1,9 +1,10 @@ using System; using System.Text; using System.Threading.Tasks; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Protocol; using MQTTnet.Core.Server; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace MQTTnet.TestApp.NetCore { @@ -11,20 +12,15 @@ namespace MQTTnet.TestApp.NetCore { public static Task RunAsync() { - MqttNetTrace.TraceMessagePublished += (s, e) => - { - Console.WriteLine($">> [{e.TraceMessage.Timestamp:O}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Source}] [{e.TraceMessage.Level}]: {e.TraceMessage.Message}"); - if (e.TraceMessage.Exception != null) - { - Console.WriteLine(e.TraceMessage.Exception); - } - }; - try { - var options = new MqttServerOptions + var services = new ServiceCollection() + .AddMqttServer() + .AddLogging(); + + services.Configure(options => { - ConnectionValidator = p => + options.ConnectionValidator = p => { if (p.ClientId == "SpecialClient") { @@ -35,22 +31,24 @@ namespace MQTTnet.TestApp.NetCore } return MqttConnectReturnCode.ConnectionAccepted; - } - }; + }; - options.Storage = new RetainedMessageHandler(); + options.Storage = new RetainedMessageHandler(); - options.ApplicationMessageInterceptor = message => - { - if (MqttTopicFilterComparer.IsMatch(message.Topic, "/myTopic/WithTimestamp/#")) + options.ApplicationMessageInterceptor = message => { - // Replace the payload with the timestamp. But also extending a JSON - // based payload with the timestamp is a suitable use case. - message.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")); - } + if (MqttTopicFilterComparer.IsMatch(message.Topic, "/myTopic/WithTimestamp/#")) + { + // Replace the payload with the timestamp. But also extending a JSON + // based payload with the timestamp is a suitable use case. + message.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")); + } - return message; - }; + return message; + }; + }); + + var serviceProvider = services.BuildServiceProvider(); //var certificate = new X509Certificate(@"C:\certs\test\test.cer", ""); //options.TlsEndpointOptions.Certificate = certificate.Export(X509ContentType.Cert); @@ -58,7 +56,7 @@ namespace MQTTnet.TestApp.NetCore //options.DefaultEndpointOptions.IsEnabled = true; //options.TlsEndpointOptions.IsEnabled = false; - var mqttServer = new MqttServerFactory().CreateMqttServer(options); + var mqttServer = serviceProvider.GetRequiredService(); mqttServer.ClientDisconnected += (s, e) => { Console.Write("Client disconnected event fired."); From 84a647e712976090742ace6f67f2a5b8830f41ae Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 22 Oct 2017 21:10:14 +0200 Subject: [PATCH 26/34] Add several new options to the WebSocket options --- Build/MQTTnet.nuspec | 1 + .../Implementations/MqttWebSocketChannel.cs | 32 +++++++++++++++++++ .../Implementations/MqttWebSocketChannel.cs | 32 +++++++++++++++++++ .../Client/MqttClientWebSocketOptions.cs | 11 ++++++- 4 files changed, 75 insertions(+), 1 deletion(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index e6ff10f3..1ed11881 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -16,6 +16,7 @@ * [Client] Added a first version of a managed client which will manage the connection, subscription etc. automatically (Thanks to @JTrotta) * [Client] The session state response from the server is now returned in the _ConnectAsync_ method and also part of the _Connected_ event args * [Client] Added a _TopicFilterBuilder_ using a fluent API (Namespace Changes!) +* [Client] Added several new options for the WebSocket channel (Thanks to @ChristianRiedl) * [Server] Added support for WebSockets via ASP.NET Core 2.0 (Thanks to @ChristianRiedl) * [Server] Added support for a custom application message interceptor diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs index 0433b80f..081c74a2 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs @@ -3,6 +3,7 @@ using MQTTnet.Core.Client; using System; using System.IO; using System.Net.WebSockets; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -32,6 +33,37 @@ namespace MQTTnet.Implementations _webSocket = new ClientWebSocket(); _webSocket.Options.KeepAliveInterval = _options.KeepAlivePeriod; + + if (_options.RequestHeaders != null) + { + foreach (var requestHeader in _options.RequestHeaders) + { + _webSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); + } + } + + if (_options.SubProtocols != null) + { + foreach (var subProtocol in _options.SubProtocols) + { + _webSocket.Options.AddSubProtocol(subProtocol); + } + } + + if (_options.CookieContainer != null) + { + _webSocket.Options.Cookies = _options.CookieContainer; + } + + if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null) + { + _webSocket.Options.ClientCertificates = new X509CertificateCollection(); + foreach (var certificate in _options.TlsOptions.Certificates) + { + _webSocket.Options.ClientCertificates.Add(new X509Certificate(certificate)); + } + } + await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None); RawReceiveStream = new WebSocketStream(_webSocket); } diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttWebSocketChannel.cs index 471cdbf8..48433165 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttWebSocketChannel.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttWebSocketChannel.cs @@ -3,6 +3,7 @@ using MQTTnet.Core.Client; using System; using System.IO; using System.Net.WebSockets; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -32,6 +33,37 @@ namespace MQTTnet.Implementations _webSocket = new ClientWebSocket(); _webSocket.Options.KeepAliveInterval = _options.KeepAlivePeriod; + + if (_options.RequestHeaders != null) + { + foreach (var requestHeader in _options.RequestHeaders) + { + _webSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); + } + } + + if (_options.SubProtocols != null) + { + foreach (var subProtocol in _options.SubProtocols) + { + _webSocket.Options.AddSubProtocol(subProtocol); + } + } + + if (_options.CookieContainer != null) + { + _webSocket.Options.Cookies = _options.CookieContainer; + } + + if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null) + { + _webSocket.Options.ClientCertificates = new X509CertificateCollection(); + foreach (var certificate in _options.TlsOptions.Certificates) + { + _webSocket.Options.ClientCertificates.Add(new X509Certificate(certificate)); + } + } + await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false); RawReceiveStream = new WebSocketStream(_webSocket); diff --git a/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs b/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs index b2b57777..522fb9bc 100644 --- a/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs +++ b/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs @@ -1,7 +1,16 @@ -namespace MQTTnet.Core.Client +using System.Collections.Generic; +using System.Net; + +namespace MQTTnet.Core.Client { public class MqttClientWebSocketOptions : BaseMqttClientOptions { public string Uri { get; set; } + + public IDictionary RequestHeaders { get; set; } + + public ICollection SubProtocols { get; set; } + + public CookieContainer CookieContainer { get; set; } } } From 7e6f4453ad921a4880d3f1f586cd70dad6b81dc0 Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Mon, 23 Oct 2017 12:42:31 +0200 Subject: [PATCH 27/34] added back MqttTrace consuming api --- Frameworks/MQTTnet.NetStandard/MqttFactory.cs | 7 ++- .../ServiceCollectionExtensions.cs | 9 +++- MQTTnet.Core/Diagnostics/MqttNetLogger.cs | 49 ++++++++++++++++++ MQTTnet.Core/Diagnostics/MqttNetTrace.cs | 33 ++++++++++++ .../MqttNetTraceMessagePublishedEventArgs.cs | 14 +++++ MQTTnet.Core/Diagnostics/MqttTraceMessage.cs | 30 +++++++++++ MQTTnet.Core/MQTTnet.Core.csproj | 1 + .../MqttLoggerProviderTest.cs | 51 +++++++++++++++++++ 8 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 MQTTnet.Core/Diagnostics/MqttNetLogger.cs create mode 100644 MQTTnet.Core/Diagnostics/MqttNetTrace.cs create mode 100644 MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs create mode 100644 MQTTnet.Core/Diagnostics/MqttTraceMessage.cs create mode 100644 Tests/MQTTnet.Core.Tests/MqttLoggerProviderTest.cs diff --git a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs index 3ac03d20..2c18ed96 100644 --- a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs +++ b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs @@ -18,11 +18,16 @@ namespace MQTTnet private static IServiceProvider BuildServiceProvider() { - return new ServiceCollection() + var serviceProvider = new ServiceCollection() .AddMqttClient() .AddMqttServer() .AddLogging() .BuildServiceProvider(); + + serviceProvider.GetRequiredService() + .AddMqttTrace(); + + return serviceProvider; } public MqttFactory() diff --git a/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs b/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs index c0578826..f8f647a9 100644 --- a/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs +++ b/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs @@ -6,6 +6,8 @@ using MQTTnet.Core.Serializer; using MQTTnet.Core.Server; using MQTTnet.Implementations; using System; +using Microsoft.Extensions.Logging; +using MQTTnet.Core.Diagnostics; namespace MQTTnet { @@ -27,7 +29,6 @@ namespace MQTTnet services.AddTransient(); services.AddTransient(); - return services; } @@ -51,5 +52,11 @@ namespace MQTTnet return services; } + + public static ILoggerFactory AddMqttTrace(this ILoggerFactory factory) + { + factory.AddProvider(new MqttNetTrace()); + return factory; + } } } diff --git a/MQTTnet.Core/Diagnostics/MqttNetLogger.cs b/MQTTnet.Core/Diagnostics/MqttNetLogger.cs new file mode 100644 index 00000000..54ab6d44 --- /dev/null +++ b/MQTTnet.Core/Diagnostics/MqttNetLogger.cs @@ -0,0 +1,49 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace MQTTnet.Core.Diagnostics +{ + public class MqttNetLogger : ILogger + { + private readonly string _categoryName; + private readonly MqttNetTrace _mqttNetTrace; + + public MqttNetLogger(string categoryName, MqttNetTrace mqttNetTrace) + { + _categoryName = categoryName; + _mqttNetTrace = mqttNetTrace; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + if (formatter == null) + { + throw new ArgumentNullException(nameof(formatter)); + } + + var message = formatter(state, exception); + + var traceMessage = new MqttNetTraceMessage(DateTime.Now, Environment.CurrentManagedThreadId, _categoryName, logLevel, message, exception); + _mqttNetTrace.Publish(traceMessage); + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + //not supported: async local requires netstandard1.3 + //for implementation see https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.Console/ConsoleLogScope.cs + public IDisposable BeginScope(TState state) + { + return new DisposableScope(); + } + + private class DisposableScope : IDisposable + { + public void Dispose() + { + } + } + } +} \ No newline at end of file diff --git a/MQTTnet.Core/Diagnostics/MqttNetTrace.cs b/MQTTnet.Core/Diagnostics/MqttNetTrace.cs new file mode 100644 index 00000000..c14d4931 --- /dev/null +++ b/MQTTnet.Core/Diagnostics/MqttNetTrace.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; + +namespace MQTTnet.Core.Diagnostics +{ + public class MqttNetTrace : ILoggerProvider + { + private readonly ConcurrentDictionary _loggers = new ConcurrentDictionary(); + + public static event EventHandler TraceMessagePublished; + + public void Publish(MqttNetTraceMessage msg) + { + TraceMessagePublished?.Invoke(this, new MqttNetTraceMessagePublishedEventArgs(msg)); + } + + public void Dispose() + { + TraceMessagePublished = null; + } + + public ILogger CreateLogger(string categoryName) + { + return _loggers.GetOrAdd(categoryName, CreateLoggerImplementation); + } + + private MqttNetLogger CreateLoggerImplementation(string categoryName) + { + return new MqttNetLogger(categoryName, this); + } + } +} diff --git a/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs b/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs new file mode 100644 index 00000000..5b8f796d --- /dev/null +++ b/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace MQTTnet.Core.Diagnostics +{ + public sealed class MqttNetTraceMessagePublishedEventArgs : EventArgs + { + public MqttNetTraceMessagePublishedEventArgs(MqttNetTraceMessage traceMessage) + { + TraceMessage = traceMessage ?? throw new ArgumentNullException(nameof(traceMessage)); + } + + public MqttNetTraceMessage TraceMessage { get; } + } +} diff --git a/MQTTnet.Core/Diagnostics/MqttTraceMessage.cs b/MQTTnet.Core/Diagnostics/MqttTraceMessage.cs new file mode 100644 index 00000000..852aae45 --- /dev/null +++ b/MQTTnet.Core/Diagnostics/MqttTraceMessage.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace MQTTnet.Core.Diagnostics +{ + public sealed class MqttNetTraceMessage + { + public MqttNetTraceMessage(DateTime timestamp, int threadId, string source, LogLevel level, string message, Exception exception) + { + Timestamp = timestamp; + ThreadId = threadId; + Source = source; + Level = level; + Message = message; + Exception = exception; + } + + public DateTime Timestamp { get; } + + public int ThreadId { get; } + + public string Source { get; } + + public LogLevel Level { get; } + + public string Message { get; } + + public Exception Exception { get; } + } +} diff --git a/MQTTnet.Core/MQTTnet.Core.csproj b/MQTTnet.Core/MQTTnet.Core.csproj index 3e877421..71a518d5 100644 --- a/MQTTnet.Core/MQTTnet.Core.csproj +++ b/MQTTnet.Core/MQTTnet.Core.csproj @@ -25,6 +25,7 @@ + \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/MqttLoggerProviderTest.cs b/Tests/MQTTnet.Core.Tests/MqttLoggerProviderTest.cs new file mode 100644 index 00000000..bda3195e --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/MqttLoggerProviderTest.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Core.Diagnostics; + +namespace MQTTnet.Core.Tests +{ + [TestClass] + public class MqttLoggerProviderTest + { + [TestMethod] + public void TestLoggerCallback() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + using ((IDisposable)serviceProvider) + { + var loggerFactory = serviceProvider.GetRequiredService(); + + loggerFactory.AddMqttTrace(); + + var expectedMsg = "Hello World!"; + MqttNetTraceMessage msg = null; + + MqttNetTrace.TraceMessagePublished += (sender, args) => + { + msg = args.TraceMessage; + }; + + var logger = loggerFactory.CreateLogger(); + + logger.LogInformation(expectedMsg); + + Assert.AreEqual(expectedMsg, msg.Message); + + var expectedException = new Exception("bad stuff"); + + logger.LogError(new EventId(), expectedException, expectedException.Message); + + Assert.AreEqual(expectedException, msg.Exception); + Assert.AreEqual(expectedException.Message, msg.Message); + } + } + } +} From 56a86b61275bdbe7fb28f57f9716a97dd442b27b Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Mon, 23 Oct 2017 14:17:06 +0200 Subject: [PATCH 28/34] adapted uwp platform projects --- .../MqttCommunicationAdapterFactory.cs | 30 ----- .../Implementations/MqttServerAdapter.cs | 15 +-- .../MQTTnet.UniversalWindows.csproj | 11 +- .../MqttClientFactory.cs | 20 ---- .../MQTTnet.UniversalWindows/MqttFactory.cs | 108 ++++++++++++++++++ .../MqttServerFactory.cs | 20 ---- .../ServiceCollectionExtensions.cs | 62 ++++++++++ .../MainPage.xaml.cs | 66 +++++------ 8 files changed, 219 insertions(+), 113 deletions(-) delete mode 100644 Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs delete mode 100644 Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs create mode 100644 Frameworks/MQTTnet.UniversalWindows/MqttFactory.cs delete mode 100644 Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs create mode 100644 Frameworks/MQTTnet.UniversalWindows/ServiceCollectionExtensions.cs diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs deleted file mode 100644 index 0654e27b..00000000 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using MQTTnet.Core.Adapter; -using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Serializer; - -namespace MQTTnet.Implementations -{ - public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory - { - public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(IMqttClientOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - - if (options is MqttClientTcpOptions tcpOptions) - { - var trace = new MqttNetTrace(); - return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }, trace); - } - - if (options is MqttClientWebSocketOptions webSocketOptions) - { - var trace = new MqttNetTrace(); - return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }, trace); - } - - throw new NotSupportedException(); - } - } -} \ No newline at end of file diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs index f5963186..373cdac4 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs @@ -1,21 +1,22 @@ using System; using System.Threading.Tasks; using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Serializer; using MQTTnet.Core.Server; using Windows.Networking.Sockets; +using Microsoft.Extensions.Logging; namespace MQTTnet.Implementations { public class MqttServerAdapter : IMqttServerAdapter, IDisposable { - private readonly MqttNetTrace _trace; + private readonly ILogger _logger; + private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory; private StreamSocketListener _defaultEndpointSocket; - public MqttServerAdapter(MqttNetTrace trace) + public MqttServerAdapter(ILogger logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory) { - _trace = trace ?? throw new ArgumentNullException(nameof(trace)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory)); } public event EventHandler ClientAccepted; @@ -56,12 +57,12 @@ namespace MQTTnet.Implementations { try { - var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer(), _trace); + var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(args.Socket)); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) { - _trace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint."); + _logger.LogError(new EventId(), exception, "Error while accepting connection at default endpoint."); } } } diff --git a/Frameworks/MQTTnet.UniversalWindows/MQTTnet.UniversalWindows.csproj b/Frameworks/MQTTnet.UniversalWindows/MQTTnet.UniversalWindows.csproj index 71081e0a..d8051f81 100644 --- a/Frameworks/MQTTnet.UniversalWindows/MQTTnet.UniversalWindows.csproj +++ b/Frameworks/MQTTnet.UniversalWindows/MQTTnet.UniversalWindows.csproj @@ -110,14 +110,13 @@ bin\x64\Release\MQTTnet.XML - - + - + @@ -127,6 +126,12 @@ + + 1.1.1 + + + 1.1.2 + 5.4.0 diff --git a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs b/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs deleted file mode 100644 index 9a254f20..00000000 --- a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.ManagedClient; -using MQTTnet.Implementations; - -namespace MQTTnet -{ - public class MqttClientFactory : IMqttClientFactory - { - public IMqttClient CreateMqttClient(IMqttNetTraceHandler traceHandler = null) - { - return new MqttClient(new MqttCommunicationAdapterFactory(), new MqttNetTrace(traceHandler)); - } - - public ManagedMqttClient CreateManagedMqttClient(IMqttNetTraceHandler traceHandler = null) - { - return new ManagedMqttClient(new MqttCommunicationAdapterFactory(), new MqttNetTrace(traceHandler)); - } - } -} \ No newline at end of file diff --git a/Frameworks/MQTTnet.UniversalWindows/MqttFactory.cs b/Frameworks/MQTTnet.UniversalWindows/MqttFactory.cs new file mode 100644 index 00000000..9761150e --- /dev/null +++ b/Frameworks/MQTTnet.UniversalWindows/MqttFactory.cs @@ -0,0 +1,108 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MQTTnet.Core.Adapter; +using MQTTnet.Core.Channel; +using MQTTnet.Core.Client; +using MQTTnet.Core.ManagedClient; +using MQTTnet.Core.Serializer; +using MQTTnet.Core.Server; +using MQTTnet.Implementations; + +namespace MQTTnet +{ + public class MqttFactory : IMqttCommunicationAdapterFactory, IMqttClientSesssionFactory, IMqttClientFactory, IMqttServerFactory + { + private readonly IServiceProvider _serviceProvider; + + private static IServiceProvider BuildServiceProvider() + { + var serviceProvider = new ServiceCollection() + .AddMqttClient() + .AddMqttServer() + .AddLogging() + .BuildServiceProvider(); + + serviceProvider.GetRequiredService() + .AddMqttTrace(); + + return serviceProvider; + } + + public MqttFactory() + : this(BuildServiceProvider()) + { + } + + public MqttFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public IMqttCommunicationAdapter CreateClientMqttCommunicationAdapter(IMqttClientOptions options) + { + var logger = _serviceProvider.GetRequiredService>(); + return new MqttChannelCommunicationAdapter(CreateMqttCommunicationChannel(options), CreateSerializer(options.ProtocolVersion), logger); + } + + public IMqttCommunicationAdapter CreateServerMqttCommunicationAdapter(IMqttCommunicationChannel channel) + { + var serializer = _serviceProvider.GetRequiredService(); + var logger = _serviceProvider.GetRequiredService>(); + return new MqttChannelCommunicationAdapter(channel, serializer, logger); + } + + public IMqttCommunicationChannel CreateMqttCommunicationChannel(IMqttClientOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + switch (options) + { + case MqttClientTcpOptions tcpOptions: + return CreateTcpChannel(tcpOptions); + case MqttClientWebSocketOptions webSocketOptions: + return CreateWebSocketChannel(webSocketOptions); + default: + throw new NotSupportedException(); + } + } + + public MqttTcpChannel CreateTcpChannel(MqttClientTcpOptions tcpOptions) + { + return new MqttTcpChannel(tcpOptions); + } + + public MqttWebSocketChannel CreateWebSocketChannel(MqttClientWebSocketOptions webSocketOptions) + { + return new MqttWebSocketChannel(webSocketOptions); + } + + public MqttPacketSerializer CreateSerializer(MqttProtocolVersion protocolVersion) + { + return new MqttPacketSerializer() + { + ProtocolVersion = protocolVersion + }; + } + + public MqttClientSession CreateClientSession(string sessionId, MqttClientSessionsManager mqttClientSessionsManager) + { + return new MqttClientSession(sessionId, mqttClientSessionsManager, _serviceProvider.GetRequiredService>(), _serviceProvider.GetRequiredService>()); + } + + public IMqttClient CreateMqttClient() + { + return _serviceProvider.GetRequiredService(); + } + + public ManagedMqttClient CreateManagedMqttClient() + { + return _serviceProvider.GetRequiredService(); + } + + public IMqttServer CreateMqttServer() + { + return _serviceProvider.GetRequiredService(); + } + } +} \ No newline at end of file diff --git a/Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs b/Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs deleted file mode 100644 index 468b8e08..00000000 --- a/Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using MQTTnet.Core.Adapter; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Server; -using MQTTnet.Implementations; - -namespace MQTTnet -{ - public class MqttServerFactory : IMqttServerFactory - { - public IMqttServer CreateMqttServer(MqttServerOptions options, IMqttNetTraceHandler traceHandler = null) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - - var trace = new MqttNetTrace(traceHandler); - return new MqttServer(options, new List { new MqttServerAdapter(trace) }, trace); - } - } -} diff --git a/Frameworks/MQTTnet.UniversalWindows/ServiceCollectionExtensions.cs b/Frameworks/MQTTnet.UniversalWindows/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..f8f647a9 --- /dev/null +++ b/Frameworks/MQTTnet.UniversalWindows/ServiceCollectionExtensions.cs @@ -0,0 +1,62 @@ +using Microsoft.Extensions.DependencyInjection; +using MQTTnet.Core.Adapter; +using MQTTnet.Core.Client; +using MQTTnet.Core.ManagedClient; +using MQTTnet.Core.Serializer; +using MQTTnet.Core.Server; +using MQTTnet.Implementations; +using System; +using Microsoft.Extensions.Logging; +using MQTTnet.Core.Diagnostics; + +namespace MQTTnet +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddMqttServer(this IServiceCollection services) + { + services.AddOptions(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + + services.AddSingleton(); + services.AddSingleton(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + return services; + } + + public static IServiceCollection AddMqttServer(this IServiceCollection services, Action configureOptions) + { + return services + .AddMqttServer() + .Configure(configureOptions); + } + + public static IServiceCollection AddMqttClient(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + return services; + } + + public static ILoggerFactory AddMqttTrace(this ILoggerFactory factory) + { + factory.AddProvider(new MqttNetTrace()); + return factory; + } + } +} diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 981d4a01..993e227e 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Windows.Security.Cryptography.Certificates; using Windows.UI.Core; using Windows.UI.Xaml; +using Microsoft.Extensions.DependencyInjection; using MQTTnet.Core; using MQTTnet.Core.Client; using MQTTnet.Core.Diagnostics; @@ -78,7 +79,7 @@ namespace MQTTnet.TestApp.UniversalWindows await _mqttClient.DisconnectAsync(); } - var factory = new MqttClientFactory(); + var factory = new MqttFactory(); _mqttClient = factory.CreateMqttClient(); await _mqttClient.ConnectAsync(options); } @@ -200,7 +201,35 @@ namespace MQTTnet.TestApp.UniversalWindows // ReSharper disable once UnusedMember.Local private async Task WikiCode() { - var mqttClient = new MqttClientFactory().CreateMqttClient(); + var services = new ServiceCollection() + .AddMqttClient() + .AddMqttServer(options => + { + options.ConnectionValidator = c => + { + if (c.ClientId.Length < 10) + { + return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected; + } + + if (c.Username != "mySecretUser") + { + return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; + } + + if (c.Password != "mySecretPassword") + { + return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; + } + + return MqttConnectReturnCode.ConnectionAccepted; + }; + }) + .BuildServiceProvider(); + + var factory = new MqttFactory(services); + + var mqttClient = factory.CreateMqttClient(); // ---------------------------------- @@ -239,9 +268,7 @@ namespace MQTTnet.TestApp.UniversalWindows // ---------------------------------- { - var options = new MqttServerOptions(); - - var mqttServer = new MqttServerFactory().CreateMqttServer(options); + var mqttServer = factory.CreateMqttServer(); await mqttServer.StartAsync(); Console.WriteLine("Press any key to exit."); @@ -251,34 +278,7 @@ namespace MQTTnet.TestApp.UniversalWindows } // ---------------------------------- - - { - var options = new MqttServerOptions - { - ConnectionValidator = c => - { - if (c.ClientId.Length < 10) - { - return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected; - } - - if (c.Username != "mySecretUser") - { - return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; - } - - if (c.Password != "mySecretPassword") - { - return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; - } - - return MqttConnectReturnCode.ConnectionAccepted; - } - }; - } - - // ---------------------------------- - + // For UWP apps: MqttTcpChannel.CustomIgnorableServerCertificateErrorsResolver = o => { From 1ca9b0bd62896c1ea79c4496d6cdb67d6f5e67c7 Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Tue, 24 Oct 2017 12:45:14 +0200 Subject: [PATCH 29/34] added aspnetcore package and adapted sample app --- .gitignore | 2 + .../ApplicationBuilderExtensions.cs | 42 +++++++++++++ .../MQTTnet.AspnetCore.csproj | 17 ++++++ .../MQTTnet.AspnetCore/MqttHostedServer.cs | 29 +++++++++ .../MqttWebSocketServerAdapter.cs | 10 +--- .../ServiceCollectionExtensions.cs | 25 ++++++++ .../ServiceCollectionExtensions.cs | 20 ++++--- MQTTnet.Core/Server/MqttServer.cs | 2 +- MQTTnet.sln | 19 ++++++ .../MQTTnet.TestApp.AspNetCore2.csproj | 10 +++- Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs | 60 ++++++++++++------- .../MQTTnet.TestApp.AspNetCore2/package.json | 14 +++++ .../MQTTnet.TestApp.AspNetCore2/tsconfig.json | 12 ++++ .../wwwroot/Index.html | 23 +++++++ .../wwwroot/app/app.ts | 16 +++++ 15 files changed, 260 insertions(+), 41 deletions(-) create mode 100644 Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs create mode 100644 Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj create mode 100644 Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs rename {Tests/MQTTnet.TestApp.AspNetCore2 => Frameworks/MQTTnet.AspnetCore}/MqttWebSocketServerAdapter.cs (86%) create mode 100644 Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs create mode 100644 Tests/MQTTnet.TestApp.AspNetCore2/package.json create mode 100644 Tests/MQTTnet.TestApp.AspNetCore2/tsconfig.json create mode 100644 Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/Index.html create mode 100644 Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts diff --git a/.gitignore b/.gitignore index b665d097..39550dc5 100644 --- a/.gitignore +++ b/.gitignore @@ -289,3 +289,5 @@ __pycache__/ Build/nuget.exe +*.js +*.map diff --git a/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs new file mode 100644 index 00000000..87317a73 --- /dev/null +++ b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using MQTTnet.AspNetCore; +using MQTTnet.Core.Server; + +namespace MQTTnet.AspnetCore +{ + public static class ApplicationBuilderExtensions + { + public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt") + { + app.UseWebSockets(); + app.Use(async (context, next) => + { + if (context.Request.Path == path && context.WebSockets.IsWebSocketRequest) + { + var adapter = app.ApplicationServices.GetRequiredService(); + using (var webSocket = await context.WebSockets.AcceptWebSocketAsync("mqtt")) + { + await adapter.AcceptWebSocketAsync(webSocket); + } + } + else + { + await next(); + } + }); + + return app; + } + + public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action configure) + { + var server = app.ApplicationServices.GetRequiredService(); + + configure(server); + + return app; + } + } +} diff --git a/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj b/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj new file mode 100644 index 00000000..5142cc15 --- /dev/null +++ b/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + + + + + + + + + + + + + diff --git a/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs b/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs new file mode 100644 index 00000000..b5326ab1 --- /dev/null +++ b/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MQTTnet.Core.Adapter; +using MQTTnet.Core.Server; + +namespace MQTTnet.AspnetCore +{ + public class MqttHostedServer : MqttServer, IHostedService + { + public MqttHostedServer(IOptions options, IEnumerable adapters, ILogger logger, MqttClientSessionsManager clientSessionsManager) + : base(options, adapters, logger, clientSessionsManager) + { + } + + public Task StartAsync(CancellationToken cancellationToken) + { + return StartAsync(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return StopAsync(); + } + } +} diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MqttWebSocketServerAdapter.cs b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs similarity index 86% rename from Tests/MQTTnet.TestApp.AspNetCore2/MqttWebSocketServerAdapter.cs rename to Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index 90ad49f2..5cd5d555 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/MqttWebSocketServerAdapter.cs +++ b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -5,21 +5,17 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Adapter; using MQTTnet.Core.Channel; -using MQTTnet.Core.Serializer; using MQTTnet.Core.Server; using MQTTnet.Implementations; -using Microsoft.Extensions.Logging; -namespace MQTTnet.TestApp.AspNetCore2 +namespace MQTTnet.AspNetCore { public class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable { - private readonly ILogger _logger; private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory; - public MqttWebSocketServerAdapter(ILogger logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory) + public MqttWebSocketServerAdapter(IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory) { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory)); } @@ -32,7 +28,7 @@ namespace MQTTnet.TestApp.AspNetCore2 public Task StopAsync() { - return Task.FromResult(0); + return Task.CompletedTask; } public Task AcceptWebSocketAsync(WebSocket webSocket) diff --git a/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..b0297b21 --- /dev/null +++ b/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using MQTTnet.AspNetCore; +using MQTTnet.Core.Adapter; +using MQTTnet.Core.Server; + +namespace MQTTnet.AspnetCore +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddHostedMqttServer(this IServiceCollection services) + { + services.AddMqttServerServices(); + + services.AddSingleton(s => s.GetService()); + services.AddSingleton(s => s.GetService()); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(s => s.GetService()); + + return services; + } + } +} diff --git a/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs b/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs index f8f647a9..019f39ba 100644 --- a/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs +++ b/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs @@ -14,15 +14,21 @@ namespace MQTTnet public static class ServiceCollectionExtensions { public static IServiceCollection AddMqttServer(this IServiceCollection services) + { + services.AddMqttServerServices(); + + services.AddSingleton(s => s.GetService()); + services.AddSingleton(); + + return services; + } + + public static IServiceCollection AddMqttServerServices(this IServiceCollection services) { services.AddOptions(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(s => s.GetService()); + services.AddSingleton(s => s.GetService()); services.AddTransient(); services.AddTransient(); @@ -42,7 +48,7 @@ namespace MQTTnet public static IServiceCollection AddMqttClient(this IServiceCollection services) { services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(s => s.GetService()); services.AddTransient(); services.AddTransient(); diff --git a/MQTTnet.Core/Server/MqttServer.cs b/MQTTnet.Core/Server/MqttServer.cs index b5c25de7..0d208c09 100644 --- a/MQTTnet.Core/Server/MqttServer.cs +++ b/MQTTnet.Core/Server/MqttServer.cs @@ -9,7 +9,7 @@ using System.Linq; namespace MQTTnet.Core.Server { - public sealed class MqttServer : IMqttServer + public class MqttServer : IMqttServer { private readonly ILogger _logger; private readonly MqttClientSessionsManager _clientSessionsManager; diff --git a/MQTTnet.sln b/MQTTnet.sln index 56a56aa5..263fee0e 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -33,6 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.NetCore", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.AspNetCore2", "Tests\MQTTnet.TestApp.AspNetCore2\MQTTnet.TestApp.AspNetCore2.csproj", "{C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.AspnetCore", "Frameworks\MQTTnet.AspnetCore\MQTTnet.AspnetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -163,6 +165,22 @@ Global {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|x64.Build.0 = Release|Any CPU {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|x86.ActiveCfg = Release|Any CPU {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|x86.Build.0 = Release|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|ARM.ActiveCfg = Debug|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|ARM.Build.0 = Debug|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|x64.ActiveCfg = Debug|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|x64.Build.0 = Debug|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|x86.ActiveCfg = Debug|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|x86.Build.0 = Debug|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|Any CPU.Build.0 = Release|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|ARM.ActiveCfg = Release|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|ARM.Build.0 = Release|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|x64.ActiveCfg = Release|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|x64.Build.0 = Release|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|x86.ActiveCfg = Release|Any CPU + {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -174,6 +192,7 @@ Global {3587E506-55A2-4EB3-99C7-DC01E42D25D2} = {32A630A7-2598-41D7-B625-204CD906F5FB} {3D283AAD-AAA8-4339-8394-52F80B6304DB} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} + {F10C4060-F7EE-4A83-919F-FF723E72F94A} = {32A630A7-2598-41D7-B625-204CD906F5FB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj index ce35f430..2f042084 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj @@ -2,10 +2,11 @@ netcoreapp2.0 + 2.3 - + @@ -13,8 +14,11 @@ - - + + + + + diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs index a4f5298f..fc52a0bc 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs @@ -1,11 +1,13 @@ -using System.Collections.Generic; -using System.Diagnostics; +using System; +using System.IO; +using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; -using MQTTnet.Core.Adapter; -using MQTTnet.Core.Server; +using MQTTnet.AspnetCore; +using MQTTnet.Core; namespace MQTTnet.TestApp.AspNetCore2 { @@ -13,35 +15,47 @@ namespace MQTTnet.TestApp.AspNetCore2 { public void ConfigureServices(IServiceCollection services) { - services.AddMqttServer(); - services.AddSingleton(); - services.AddSingleton(); + services.AddHostedMqttServer(); } - public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(LogLevel.Debug); loggerFactory.AddDebug(); - - var adapter = app.ApplicationServices.GetService(); - var mqttServer = app.ApplicationServices.GetService(); - await mqttServer.StartAsync(); - app.UseWebSockets(); - app.Use(async (context, next) => + app.UseMqttEndpoint(); + app.UseMqttServer(async server => { - if (context.WebSockets.IsWebSocketRequest) + var msg = new MqttApplicationMessageBuilder() + .WithPayload("Mqtt is Awesome") + .WithTopic("message") + .Build(); + + while (true) { - using (var webSocket = await context.WebSockets.AcceptWebSocketAsync()) - { - await adapter.AcceptWebSocketAsync(webSocket); - } - } - else - { - await next(); + server.Publish(msg); + await Task.Delay(TimeSpan.FromSeconds(2)); } }); + + app.Use((context, next) => + { + if (context.Request.Path == "/") + { + context.Request.Path = "/Index.html"; + } + + return next(); + }); + + app.UseStaticFiles(); + + + app.UseStaticFiles( new StaticFileOptions() + { + RequestPath = "/node_modules", + FileProvider = new PhysicalFileProvider( Path.Combine(env.ContentRootPath, "node_modules" ) ) + } ); } } } diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/package.json b/Tests/MQTTnet.TestApp.AspNetCore2/package.json new file mode 100644 index 00000000..ad48ca10 --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/package.json @@ -0,0 +1,14 @@ +{ + "version": "1.0.0", + "name": "mqtt.test", + "private": true, + "devDependencies": { + }, + + "dependencies": { + "mqtt": "2.13.1", + "@types/node": "8.0.46", + "systemjs": "0.20.19", + "typescript": "2.3.4" + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/tsconfig.json b/Tests/MQTTnet.TestApp.AspNetCore2/tsconfig.json new file mode 100644 index 00000000..5794ac5b --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "noImplicitAny": false, + "noEmitOnError": true, + "removeComments": false, + "sourceMap": true, + "target": "es5" + }, + "exclude": [ + "node_modules" + ] +} diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/Index.html b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/Index.html new file mode 100644 index 00000000..522f0024 --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/Index.html @@ -0,0 +1,23 @@ + + + + + MQTT test + + + + + + \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts new file mode 100644 index 00000000..0a801c4b --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts @@ -0,0 +1,16 @@ +import { connect } from "mqtt"; + +var client = connect('ws://' + location.host + '/mqtt', + { + clientId: "client1", + }); + +client.on('connect', () => { + client.subscribe('presence'); + client.publish('presence', 'Hello mqtt'); +}); + +client.on('message', (topic, message) => { + // message is Buffer + console.log(message.toString()); +}) \ No newline at end of file From d594b1447fec66b970d84cb5efcc9fa42c5e96e7 Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Tue, 24 Oct 2017 12:45:26 +0200 Subject: [PATCH 30/34] fixed index out of range --- .../MQTTnet.NetStandard/Implementations/WebSocketStream.cs | 1 + .../Implementations/WebSocketStream.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs index d38de6f1..f037b380 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs @@ -41,6 +41,7 @@ namespace MQTTnet.Implementations { var response = await _webSocket.ReceiveAsync(new ArraySegment(buffer, currentOffset, count), cancellationToken).ConfigureAwait(false); currentOffset += response.Count; + count -= response.Count; if (response.MessageType == WebSocketMessageType.Close) { diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs index 1843c6a6..2a269c00 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs @@ -27,7 +27,8 @@ namespace MQTTnet.Implementations { var response = await _webSocket.ReceiveAsync(new ArraySegment(buffer, currentOffset, count), cancellationToken).ConfigureAwait(false); currentOffset += response.Count; - + count -= response.Count; + if (response.MessageType == WebSocketMessageType.Close) { await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); From a68343a306c2a561ab7e9ed2b23790da774f6310 Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Tue, 24 Oct 2017 13:14:18 +0200 Subject: [PATCH 31/34] fixed duplicated console log --- Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs index fc52a0bc..56df889a 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs @@ -20,9 +20,6 @@ namespace MQTTnet.TestApp.AspNetCore2 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory.AddConsole(LogLevel.Debug); - loggerFactory.AddDebug(); - app.UseMqttEndpoint(); app.UseMqttServer(async server => { From e80d21e8520879b1b8db9ed0293870f03352e43d Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Tue, 24 Oct 2017 13:15:02 +0200 Subject: [PATCH 32/34] generate exception in case websocket is closed so stream does not return array of 255 --- .../MQTTnet.NetStandard/Implementations/WebSocketStream.cs | 6 ++++++ .../Implementations/WebSocketStream.cs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs index f037b380..c27e4801 100644 --- a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs +++ b/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs @@ -3,6 +3,7 @@ using System.IO; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; +using MQTTnet.Core.Exceptions; namespace MQTTnet.Implementations { @@ -49,6 +50,11 @@ namespace MQTTnet.Implementations } } + if (_webSocket.State == WebSocketState.Closed) + { + throw new MqttCommunicationException( "connection closed" ); + } + return currentOffset - offset; } diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs index 2a269c00..718b5eec 100644 --- a/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs +++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs @@ -3,6 +3,7 @@ using System.IO; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; +using MQTTnet.Core.Exceptions; namespace MQTTnet.Implementations { @@ -35,6 +36,11 @@ namespace MQTTnet.Implementations } } + if (_webSocket.State == WebSocketState.Closed) + { + throw new MqttCommunicationException("connection closed"); + } + return currentOffset - offset; } From 0e0fcabc04fc106718d6c20eb8c43dc04a312f1b Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Tue, 24 Oct 2017 13:15:35 +0200 Subject: [PATCH 33/34] gently close the connection before leaving the page --- Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts index 0a801c4b..166a75a2 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts +++ b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts @@ -13,4 +13,8 @@ client.on('connect', () => { client.on('message', (topic, message) => { // message is Buffer console.log(message.toString()); -}) \ No newline at end of file +}); + +window.onbeforeunload = () => { + client.end(); +}; \ No newline at end of file From 2cd3bd7588d0fc46988fe5daaa9267bb201d0d41 Mon Sep 17 00:00:00 2001 From: Jan Eggers Date: Tue, 24 Oct 2017 15:35:02 +0200 Subject: [PATCH 34/34] added some ui to send test msgs --- Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs | 8 ++-- .../wwwroot/Index.html | 12 +++++ .../wwwroot/app/app.ts | 48 ++++++++++++++++--- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs index 56df889a..a71978c3 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs @@ -24,14 +24,14 @@ namespace MQTTnet.TestApp.AspNetCore2 app.UseMqttServer(async server => { var msg = new MqttApplicationMessageBuilder() - .WithPayload("Mqtt is Awesome") - .WithTopic("message") - .Build(); + .WithPayload("Mqtt is awesome") + .WithTopic("message"); while (true) { - server.Publish(msg); + server.Publish(msg.Build()); await Task.Delay(TimeSpan.FromSeconds(2)); + msg.WithPayload("Mqtt is still awesome at " + DateTime.Now); } }); diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/Index.html b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/Index.html index 522f0024..9cc67e4d 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/Index.html +++ b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/Index.html @@ -19,5 +19,17 @@ }); System.import('app/app'); + +
+

+ + + +
+ +
+
    +
    + \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts index 166a75a2..3ce98566 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts +++ b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts @@ -5,16 +5,52 @@ var client = connect('ws://' + location.host + '/mqtt', clientId: "client1", }); +window.onbeforeunload = () => { + client.end(); +}; + +var publishButton = document.getElementById("publish"); +var topicInput = document.getElementById("topic"); +var msgInput = document.getElementById("msg"); +var stateParagraph = document.getElementById("state"); +var msgsList = document.getElementById("msgs"); + +publishButton.onclick = click => { + var topic = topicInput.value; + var msg = msgInput.value; + client.publish(topic, msg); +}; + client.on('connect', () => { - client.subscribe('presence'); + client.subscribe('#', { qos: 0 }, (err, granted) => { + console.log(err); + }); client.publish('presence', 'Hello mqtt'); + + stateParagraph.innerText = "connected"; +}); + +client.on("error", e => { + showMsg("error: " + e.message); +}); + +client.on("reconnect", () => { + stateParagraph.innerText = "reconnecting"; }); client.on('message', (topic, message) => { - // message is Buffer - console.log(message.toString()); + showMsg(topic + ": " + message.toString()); }); -window.onbeforeunload = () => { - client.end(); -}; \ No newline at end of file +function showMsg(msg: string) { + //console.log(msg); + + var node = document.createElement("LI"); + node.appendChild(document.createTextNode(msg)); + + msgsList.appendChild(node); + + if (msgsList.childElementCount > 20) { + msgsList.removeChild(msgsList.childNodes[0]); + } +} \ No newline at end of file