@@ -14,10 +14,14 @@ | |||||
* [Core] Added a strong name for the assembly. | * [Core] Added a strong name for the assembly. | ||||
* [Core] Performance optimizations. | * [Core] Performance optimizations. | ||||
* [Core] Fixed a logging issue when dealing with IOExceptions. | * [Core] Fixed a logging issue when dealing with IOExceptions. | ||||
* [Core] Fixed a typo in the global logger class (BREAKING CHANGE! Please find new example in Wiki). | |||||
* [Client] Fixed an issue in _ManagedClient_ which can cause the client to stop when publishing subscriptions. | * [Client] Fixed an issue in _ManagedClient_ which can cause the client to stop when publishing subscriptions. | ||||
* [Server] The application message interceptor can now delete any received application message. | * [Server] The application message interceptor can now delete any received application message. | ||||
* [Server] Added a ConnectionValidator context to align with other APIs. | |||||
* [Server] Added a ConnectionValidator context to align with other APIs (BREAKING CHANGE! Please find new example in Wiki). | |||||
* [Server] Added an interface for the _MqttServerOptions_. | * [Server] Added an interface for the _MqttServerOptions_. | ||||
* [Server] Added packet statistics for the connected clients. | |||||
* [Server] Fixed a security issue which sends retained packages to a failed subscription. | |||||
* [Server] Fixed the response (MaximumQoS) of a subscription (Thanks to @redbeans2017). | |||||
</releaseNotes> | </releaseNotes> | ||||
<copyright>Copyright Christian Kratky 2016-2017</copyright> | <copyright>Copyright Christian Kratky 2016-2017</copyright> | ||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | ||||
@@ -0,0 +1,30 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks> | |||||
<AssemblyVersion>0.0.0.0</AssemblyVersion> | |||||
<FileVersion>0.0.0.0</FileVersion> | |||||
<Product /> | |||||
<Company /> | |||||
<Authors /> | |||||
<PackageId /> | |||||
<Version>0.0.0.0</Version> | |||||
</PropertyGroup> | |||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'"> | |||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies> | |||||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker> | |||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier> | |||||
<TargetPlatformVersion>10.0.16299.0</TargetPlatformVersion> | |||||
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion> | |||||
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier> | |||||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion> | |||||
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants> | |||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj" /> | |||||
</ItemGroup> | |||||
</Project> |
@@ -0,0 +1,101 @@ | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Threading.Tasks; | |||||
using MQTTnet.Client; | |||||
using MQTTnet.Internal; | |||||
using MQTTnet.Protocol; | |||||
namespace MQTTnet.Extensions.Rpc | |||||
{ | |||||
public sealed class MqttRpcClient : IDisposable | |||||
{ | |||||
private const string ResponseTopic = "$RPC/+/+/response"; | |||||
private readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>(); | |||||
private readonly IMqttClient _mqttClient; | |||||
private bool _isEnabled; | |||||
public MqttRpcClient(IMqttClient mqttClient) | |||||
{ | |||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); | |||||
_mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; | |||||
} | |||||
public async Task EnableAsync() | |||||
{ | |||||
await _mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(ResponseTopic).WithAtLeastOnceQoS().Build()); | |||||
_isEnabled = true; | |||||
} | |||||
public async Task DisableAsync() | |||||
{ | |||||
await _mqttClient.UnsubscribeAsync(ResponseTopic); | |||||
_isEnabled = false; | |||||
} | |||||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel) | |||||
{ | |||||
if (methodName == null) throw new ArgumentNullException(nameof(methodName)); | |||||
if (methodName.Contains("/") || methodName.Contains("+") || methodName.Contains("#")) | |||||
{ | |||||
throw new ArgumentException("The method name cannot contain /, + or #."); | |||||
} | |||||
if (!_isEnabled) | |||||
{ | |||||
throw new InvalidOperationException("The RPC client is not enabled."); | |||||
} | |||||
var requestTopic = $"$MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}"; | |||||
var responseTopic = requestTopic + "/response"; | |||||
var requestMessage = new MqttApplicationMessageBuilder() | |||||
.WithTopic(requestTopic) | |||||
.WithPayload(payload) | |||||
.WithQualityOfServiceLevel(qualityOfServiceLevel) | |||||
.Build(); | |||||
try | |||||
{ | |||||
var tcs = new TaskCompletionSource<byte[]>(); | |||||
if (!_waitingCalls.TryAdd(responseTopic, tcs)) | |||||
{ | |||||
throw new InvalidOperationException(); | |||||
} | |||||
await _mqttClient.PublishAsync(requestMessage); | |||||
return await tcs.Task.TimeoutAfter(timeout); | |||||
} | |||||
finally | |||||
{ | |||||
_waitingCalls.TryRemove(responseTopic, out _); | |||||
} | |||||
} | |||||
private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs) | |||||
{ | |||||
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out TaskCompletionSource<byte[]> tcs)) | |||||
{ | |||||
return; | |||||
} | |||||
if (tcs.Task.IsCompleted || tcs.Task.IsCanceled) | |||||
{ | |||||
return; | |||||
} | |||||
tcs.TrySetResult(eventArgs.ApplicationMessage.Payload); | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
foreach (var tcs in _waitingCalls) | |||||
{ | |||||
tcs.Value.SetCanceled(); | |||||
} | |||||
_waitingCalls.Clear(); | |||||
} | |||||
} | |||||
} |
@@ -2,8 +2,13 @@ | |||||
<PropertyGroup> | <PropertyGroup> | ||||
<TargetFramework>netstandard2.0</TargetFramework> | <TargetFramework>netstandard2.0</TargetFramework> | ||||
<AssemblyVersion>2.5.2.0</AssemblyVersion> | |||||
<FileVersion>2.5.2.0</FileVersion> | |||||
<AssemblyVersion>0.0.0.0</AssemblyVersion> | |||||
<FileVersion>0.0.0.0</FileVersion> | |||||
<Version>0.0.0.0</Version> | |||||
<Product /> | |||||
<Company /> | |||||
<Authors /> | |||||
<PackageId /> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> | ||||
@@ -18,6 +18,8 @@ namespace MQTTnet.Adapter | |||||
{ | { | ||||
private const uint ErrorOperationAborted = 0x800703E3; | private const uint ErrorOperationAborted = 0x800703E3; | ||||
private static readonly byte[] EmptyBody = new byte[0]; | |||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | ||||
private readonly IMqttNetLogger _logger; | private readonly IMqttNetLogger _logger; | ||||
private readonly IMqttChannel _channel; | private readonly IMqttChannel _channel; | ||||
@@ -89,35 +91,28 @@ namespace MQTTnet.Adapter | |||||
MqttBasePacket packet = null; | MqttBasePacket packet = null; | ||||
await ExecuteAndWrapExceptionAsync(async () => | await ExecuteAndWrapExceptionAsync(async () => | ||||
{ | { | ||||
ReceivedMqttPacket receivedMqttPacket = null; | |||||
try | |||||
ReceivedMqttPacket receivedMqttPacket; | |||||
if (timeout > TimeSpan.Zero) | |||||
{ | { | ||||
if (timeout > TimeSpan.Zero) | |||||
{ | |||||
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); | |||||
} | |||||
else | |||||
{ | |||||
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false); | |||||
} | |||||
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested) | |||||
{ | |||||
throw new TaskCanceledException(); | |||||
} | |||||
packet = PacketSerializer.Deserialize(receivedMqttPacket); | |||||
if (packet == null) | |||||
{ | |||||
throw new MqttProtocolViolationException("Received malformed packet."); | |||||
} | |||||
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); | |||||
} | |||||
else | |||||
{ | |||||
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false); | |||||
} | |||||
_logger.Trace<MqttChannelAdapter>("RX <<< {0}", packet); | |||||
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested) | |||||
{ | |||||
throw new TaskCanceledException(); | |||||
} | } | ||||
finally | |||||
packet = PacketSerializer.Deserialize(receivedMqttPacket.Header, receivedMqttPacket.Body); | |||||
if (packet == null) | |||||
{ | { | ||||
receivedMqttPacket?.Dispose(); | |||||
throw new MqttProtocolViolationException("Received malformed packet."); | |||||
} | } | ||||
_logger.Trace<MqttChannelAdapter>("RX <<< {0}", packet); | |||||
}).ConfigureAwait(false); | }).ConfigureAwait(false); | ||||
return packet; | return packet; | ||||
@@ -133,7 +128,7 @@ namespace MQTTnet.Adapter | |||||
if (header.BodyLength == 0) | if (header.BodyLength == 0) | ||||
{ | { | ||||
return new ReceivedMqttPacket(header, new MemoryStream(0)); | |||||
return new ReceivedMqttPacket(header, EmptyBody); | |||||
} | } | ||||
var body = new byte[header.BodyLength]; | var body = new byte[header.BodyLength]; | ||||
@@ -145,7 +140,7 @@ namespace MQTTnet.Adapter | |||||
offset += readBytesCount; | offset += readBytesCount; | ||||
} while (offset < header.BodyLength); | } while (offset < header.BodyLength); | ||||
return new ReceivedMqttPacket(header, new MemoryStream(body, 0, body.Length, false, true)); | |||||
return new ReceivedMqttPacket(header, body); | |||||
} | } | ||||
private static async Task ExecuteAndWrapExceptionAsync(Func<Task> action) | private static async Task ExecuteAndWrapExceptionAsync(Func<Task> action) | ||||
@@ -1,12 +1,11 @@ | |||||
using System; | using System; | ||||
using System.IO; | |||||
using MQTTnet.Packets; | using MQTTnet.Packets; | ||||
namespace MQTTnet.Adapter | namespace MQTTnet.Adapter | ||||
{ | { | ||||
public sealed class ReceivedMqttPacket : IDisposable | |||||
public class ReceivedMqttPacket | |||||
{ | { | ||||
public ReceivedMqttPacket(MqttPacketHeader header, MemoryStream body) | |||||
public ReceivedMqttPacket(MqttPacketHeader header, byte[] body) | |||||
{ | { | ||||
Header = header ?? throw new ArgumentNullException(nameof(header)); | Header = header ?? throw new ArgumentNullException(nameof(header)); | ||||
Body = body ?? throw new ArgumentNullException(nameof(body)); | Body = body ?? throw new ArgumentNullException(nameof(body)); | ||||
@@ -14,11 +13,6 @@ namespace MQTTnet.Adapter | |||||
public MqttPacketHeader Header { get; } | public MqttPacketHeader Header { get; } | ||||
public MemoryStream Body { get; } | |||||
public void Dispose() | |||||
{ | |||||
Body?.Dispose(); | |||||
} | |||||
public byte[] Body { get; } | |||||
} | } | ||||
} | } |
@@ -70,7 +70,7 @@ namespace MQTTnet.Implementations | |||||
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); | _sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); | ||||
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); | await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); | ||||
} | } | ||||
CreateStreams(_socket, _sslStream); | CreateStreams(_socket, _sslStream); | ||||
} | } | ||||
@@ -139,10 +139,7 @@ namespace MQTTnet.Implementations | |||||
private void CreateStreams(Socket socket, Stream sslStream) | private void CreateStreams(Socket socket, Stream sslStream) | ||||
{ | { | ||||
var stream = sslStream ?? new NetworkStream(socket); | var stream = 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 | //todo: if branch can be used with min dependency NetStandard1.6 | ||||
#if NET452 || NET461 | #if NET452 || NET461 | ||||
SendStream = new BufferedStream(stream, BufferSize); | SendStream = new BufferedStream(stream, BufferSize); | ||||
@@ -6,8 +6,8 @@ | |||||
<RootNamespace>MQTTnet</RootNamespace> | <RootNamespace>MQTTnet</RootNamespace> | ||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> | <GeneratePackageOnBuild>False</GeneratePackageOnBuild> | ||||
<DebugType>Full</DebugType> | <DebugType>Full</DebugType> | ||||
<AssemblyVersion>2.5.3.0</AssemblyVersion> | |||||
<FileVersion>2.5.3.0</FileVersion> | |||||
<AssemblyVersion>0.0.0.0</AssemblyVersion> | |||||
<FileVersion>0.0.0.0</FileVersion> | |||||
<Version>0.0.0.0</Version> | <Version>0.0.0.0</Version> | ||||
<Company /> | <Company /> | ||||
<Product /> | <Product /> | ||||
@@ -29,7 +29,6 @@ | |||||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion> | <TargetFrameworkVersion>v5.0</TargetFrameworkVersion> | ||||
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants> | <DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants> | ||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> | <LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" /> | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" /> | ||||
@@ -13,9 +13,9 @@ namespace MQTTnet.ManagedClient | |||||
{ | { | ||||
public class ManagedMqttClient : IManagedMqttClient | public class ManagedMqttClient : IManagedMqttClient | ||||
{ | { | ||||
private readonly ManagedMqttClientStorageManager _storageManager = new ManagedMqttClientStorageManager(); | |||||
private readonly BlockingCollection<MqttApplicationMessage> _messageQueue = new BlockingCollection<MqttApplicationMessage>(); | private readonly BlockingCollection<MqttApplicationMessage> _messageQueue = new BlockingCollection<MqttApplicationMessage>(); | ||||
private readonly HashSet<TopicFilter> _subscriptions = new HashSet<TopicFilter>(); | private readonly HashSet<TopicFilter> _subscriptions = new HashSet<TopicFilter>(); | ||||
private readonly SemaphoreSlim _subscriptionsSemaphore = new SemaphoreSlim(1, 1); | |||||
private readonly IMqttClient _mqttClient; | private readonly IMqttClient _mqttClient; | ||||
private readonly IMqttNetLogger _logger; | private readonly IMqttNetLogger _logger; | ||||
@@ -23,7 +23,9 @@ namespace MQTTnet.ManagedClient | |||||
private CancellationTokenSource _connectionCancellationToken; | private CancellationTokenSource _connectionCancellationToken; | ||||
private CancellationTokenSource _publishingCancellationToken; | private CancellationTokenSource _publishingCancellationToken; | ||||
private ManagedMqttClientStorageManager _storageManager; | |||||
private IManagedMqttClientOptions _options; | private IManagedMqttClientOptions _options; | ||||
private bool _subscriptionsNotPushed; | private bool _subscriptionsNotPushed; | ||||
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger) | public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger) | ||||
@@ -55,15 +57,11 @@ namespace MQTTnet.ManagedClient | |||||
if (_connectionCancellationToken != null) throw new InvalidOperationException("The managed client is already started."); | if (_connectionCancellationToken != null) throw new InvalidOperationException("The managed client is already started."); | ||||
_options = options; | _options = options; | ||||
await _storageManager.SetStorageAsync(_options.Storage).ConfigureAwait(false); | |||||
if (_options.Storage != null) | if (_options.Storage != null) | ||||
{ | { | ||||
var loadedMessages = await _options.Storage.LoadQueuedMessagesAsync().ConfigureAwait(false); | |||||
foreach (var loadedMessage in loadedMessages) | |||||
{ | |||||
_messageQueue.Add(loadedMessage); | |||||
} | |||||
_storageManager = new ManagedMqttClientStorageManager(_options.Storage); | |||||
await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false); | |||||
} | } | ||||
_connectionCancellationToken = new CancellationTokenSource(); | _connectionCancellationToken = new CancellationTokenSource(); | ||||
@@ -97,16 +95,21 @@ namespace MQTTnet.ManagedClient | |||||
foreach (var applicationMessage in applicationMessages) | foreach (var applicationMessage in applicationMessages) | ||||
{ | { | ||||
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false); | |||||
if (_storageManager != null) | |||||
{ | |||||
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false); | |||||
} | |||||
_messageQueue.Add(applicationMessage); | _messageQueue.Add(applicationMessage); | ||||
} | } | ||||
} | } | ||||
public Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters) | |||||
public async Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters) | |||||
{ | { | ||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); | if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); | ||||
lock (_subscriptions) | |||||
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false); | |||||
try | |||||
{ | { | ||||
foreach (var topicFilter in topicFilters) | foreach (var topicFilter in topicFilters) | ||||
{ | { | ||||
@@ -116,13 +119,16 @@ namespace MQTTnet.ManagedClient | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return Task.FromResult(0); | |||||
finally | |||||
{ | |||||
_subscriptionsSemaphore.Release(); | |||||
} | |||||
} | } | ||||
public Task UnsubscribeAsync(IEnumerable<TopicFilter> topicFilters) | |||||
public async Task UnsubscribeAsync(IEnumerable<TopicFilter> topicFilters) | |||||
{ | { | ||||
lock (_subscriptions) | |||||
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false); | |||||
try | |||||
{ | { | ||||
foreach (var topicFilter in topicFilters) | foreach (var topicFilter in topicFilters) | ||||
{ | { | ||||
@@ -132,8 +138,10 @@ namespace MQTTnet.ManagedClient | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return Task.FromResult(0); | |||||
finally | |||||
{ | |||||
_subscriptionsSemaphore.Release(); | |||||
} | |||||
} | } | ||||
private async Task MaintainConnectionAsync(CancellationToken cancellationToken) | private async Task MaintainConnectionAsync(CancellationToken cancellationToken) | ||||
@@ -242,7 +250,11 @@ namespace MQTTnet.ManagedClient | |||||
try | try | ||||
{ | { | ||||
await _mqttClient.PublishAsync(message).ConfigureAwait(false); | await _mqttClient.PublishAsync(message).ConfigureAwait(false); | ||||
await _storageManager.RemoveAsync(message).ConfigureAwait(false); | |||||
if (_storageManager != null) | |||||
{ | |||||
await _storageManager.RemoveAsync(message).ConfigureAwait(false); | |||||
} | |||||
} | } | ||||
catch (MqttCommunicationException exception) | catch (MqttCommunicationException exception) | ||||
{ | { | ||||
@@ -264,13 +276,18 @@ namespace MQTTnet.ManagedClient | |||||
_logger.Info<ManagedMqttClient>(nameof(ManagedMqttClient), "Synchronizing subscriptions"); | _logger.Info<ManagedMqttClient>(nameof(ManagedMqttClient), "Synchronizing subscriptions"); | ||||
List<TopicFilter> subscriptions; | List<TopicFilter> subscriptions; | ||||
lock (_subscriptions) | |||||
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false); | |||||
try | |||||
{ | { | ||||
subscriptions = _subscriptions.ToList(); | subscriptions = _subscriptions.ToList(); | ||||
_subscriptionsNotPushed = false; | _subscriptionsNotPushed = false; | ||||
} | } | ||||
finally | |||||
{ | |||||
_subscriptionsSemaphore.Release(); | |||||
} | |||||
if (!_subscriptions.Any()) | |||||
if (!subscriptions.Any()) | |||||
{ | { | ||||
return; | return; | ||||
} | } | ||||
@@ -1,4 +1,5 @@ | |||||
using System.Collections.Generic; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -8,18 +9,19 @@ namespace MQTTnet.ManagedClient | |||||
{ | { | ||||
private readonly List<MqttApplicationMessage> _applicationMessages = new List<MqttApplicationMessage>(); | private readonly List<MqttApplicationMessage> _applicationMessages = new List<MqttApplicationMessage>(); | ||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | ||||
private IManagedMqttClientStorage _storage; | |||||
private readonly IManagedMqttClientStorage _storage; | |||||
public async Task SetStorageAsync(IManagedMqttClientStorage storage) | |||||
public ManagedMqttClientStorageManager(IManagedMqttClientStorage storage) | |||||
{ | { | ||||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
try | |||||
{ | |||||
_storage = storage; | |||||
} | |||||
finally | |||||
_storage = storage ?? throw new ArgumentNullException(nameof(storage)); | |||||
} | |||||
public async Task LoadQueuedMessagesAsync() | |||||
{ | |||||
var loadedMessages = await _storage.LoadQueuedMessagesAsync().ConfigureAwait(false); | |||||
foreach (var loadedMessage in loadedMessages) | |||||
{ | { | ||||
_semaphore.Release(); | |||||
_applicationMessages.Add(loadedMessage); | |||||
} | } | ||||
} | } | ||||
@@ -28,11 +30,6 @@ namespace MQTTnet.ManagedClient | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | await _semaphore.WaitAsync().ConfigureAwait(false); | ||||
try | try | ||||
{ | { | ||||
if (_storage == null) | |||||
{ | |||||
return; | |||||
} | |||||
_applicationMessages.Add(applicationMessage); | _applicationMessages.Add(applicationMessage); | ||||
await SaveAsync().ConfigureAwait(false); | await SaveAsync().ConfigureAwait(false); | ||||
} | } | ||||
@@ -47,11 +44,6 @@ namespace MQTTnet.ManagedClient | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | await _semaphore.WaitAsync().ConfigureAwait(false); | ||||
try | try | ||||
{ | { | ||||
if (_storage == null) | |||||
{ | |||||
return; | |||||
} | |||||
var index = _applicationMessages.IndexOf(applicationMessage); | var index = _applicationMessages.IndexOf(applicationMessage); | ||||
if (index == -1) | if (index == -1) | ||||
{ | { | ||||
@@ -1,6 +1,5 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using MQTTnet.Adapter; | |||||
using MQTTnet.Packets; | using MQTTnet.Packets; | ||||
namespace MQTTnet.Serializer | namespace MQTTnet.Serializer | ||||
@@ -9,8 +8,8 @@ namespace MQTTnet.Serializer | |||||
{ | { | ||||
MqttProtocolVersion ProtocolVersion { get; set; } | MqttProtocolVersion ProtocolVersion { get; set; } | ||||
IEnumerable<ArraySegment<byte>> Serialize(MqttBasePacket mqttPacket); | |||||
ICollection<ArraySegment<byte>> Serialize(MqttBasePacket mqttPacket); | |||||
MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket); | |||||
MqttBasePacket Deserialize(MqttPacketHeader header, byte[] body); | |||||
} | } | ||||
} | } |
@@ -1,9 +1,9 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | |||||
using System.IO; | using System.IO; | ||||
using System.Text; | using System.Text; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using MQTTnet.Adapter; | |||||
using MQTTnet.Exceptions; | using MQTTnet.Exceptions; | ||||
using MQTTnet.Packets; | using MQTTnet.Packets; | ||||
using MQTTnet.Protocol; | using MQTTnet.Protocol; | ||||
@@ -12,15 +12,15 @@ namespace MQTTnet.Serializer | |||||
{ | { | ||||
public sealed class MqttPacketReader : BinaryReader | public sealed class MqttPacketReader : BinaryReader | ||||
{ | { | ||||
private readonly ReceivedMqttPacket _receivedMqttPacket; | |||||
public MqttPacketReader(ReceivedMqttPacket receivedMqttPacket) | |||||
: base(receivedMqttPacket.Body, Encoding.UTF8, true) | |||||
private readonly MqttPacketHeader _header; | |||||
public MqttPacketReader(MqttPacketHeader header, Stream bodyStream) | |||||
: base(bodyStream, Encoding.UTF8, true) | |||||
{ | { | ||||
_receivedMqttPacket = receivedMqttPacket; | |||||
_header = header; | |||||
} | } | ||||
public bool EndOfRemainingData => BaseStream.Position == _receivedMqttPacket.Header.BodyLength; | |||||
public bool EndOfRemainingData => BaseStream.Position == _header.BodyLength; | |||||
public static MqttPacketHeader ReadHeaderFromSource(Stream stream, CancellationToken cancellationToken) | public static MqttPacketHeader ReadHeaderFromSource(Stream stream, CancellationToken cancellationToken) | ||||
{ | { | ||||
@@ -77,7 +77,7 @@ namespace MQTTnet.Serializer | |||||
public byte[] ReadRemainingData() | public byte[] ReadRemainingData() | ||||
{ | { | ||||
return ReadBytes(_receivedMqttPacket.Header.BodyLength - (int)BaseStream.Position); | |||||
return ReadBytes(_header.BodyLength - (int)BaseStream.Position); | |||||
} | } | ||||
private static int ReadBodyLengthFromSource(Stream stream, CancellationToken cancellationToken) | private static int ReadBodyLengthFromSource(Stream stream, CancellationToken cancellationToken) | ||||
@@ -87,7 +87,7 @@ namespace MQTTnet.Serializer | |||||
var value = 0; | var value = 0; | ||||
byte encodedByte; | byte encodedByte; | ||||
////var readBytes = new List<int>(); | |||||
var readBytes = new List<byte>(); | |||||
do | do | ||||
{ | { | ||||
if (cancellationToken.IsCancellationRequested) | if (cancellationToken.IsCancellationRequested) | ||||
@@ -101,15 +101,14 @@ namespace MQTTnet.Serializer | |||||
throw new MqttCommunicationException("Connection closed while reading remaining length data."); | throw new MqttCommunicationException("Connection closed while reading remaining length data."); | ||||
} | } | ||||
////readBytes.Add(buffer); | |||||
encodedByte = (byte)buffer; | encodedByte = (byte)buffer; | ||||
readBytes.Add(encodedByte); | |||||
value += (byte)(encodedByte & 127) * multiplier; | value += (byte)(encodedByte & 127) * multiplier; | ||||
multiplier *= 128; | multiplier *= 128; | ||||
if (multiplier > 128 * 128 * 128) | if (multiplier > 128 * 128 * 128) | ||||
{ | { | ||||
//throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)})."); | |||||
throw new MqttProtocolViolationException("Remaining length is invalid."); | |||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)})."); | |||||
} | } | ||||
} while ((encodedByte & 128) != 0); | } while ((encodedByte & 128) != 0); | ||||
@@ -1,5 +1,4 @@ | |||||
using MQTTnet.Adapter; | |||||
using MQTTnet.Exceptions; | |||||
using MQTTnet.Exceptions; | |||||
using MQTTnet.Packets; | using MQTTnet.Packets; | ||||
using MQTTnet.Protocol; | using MQTTnet.Protocol; | ||||
using System; | using System; | ||||
@@ -17,7 +16,7 @@ namespace MQTTnet.Serializer | |||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; | public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; | ||||
public IEnumerable<ArraySegment<byte>> Serialize(MqttBasePacket packet) | |||||
public ICollection<ArraySegment<byte>> Serialize(MqttBasePacket packet) | |||||
{ | { | ||||
if (packet == null) throw new ArgumentNullException(nameof(packet)); | if (packet == null) throw new ArgumentNullException(nameof(packet)); | ||||
@@ -43,13 +42,15 @@ namespace MQTTnet.Serializer | |||||
} | } | ||||
} | } | ||||
public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket) | |||||
public MqttBasePacket Deserialize(MqttPacketHeader header, byte[] body) | |||||
{ | { | ||||
if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket)); | |||||
if (header == null) throw new ArgumentNullException(nameof(header)); | |||||
if (body == null) throw new ArgumentNullException(nameof(body)); | |||||
using (var reader = new MqttPacketReader(receivedMqttPacket)) | |||||
using (var bodyStream = new MemoryStream(body)) | |||||
using (var reader = new MqttPacketReader(header, bodyStream)) | |||||
{ | { | ||||
return Deserialize(receivedMqttPacket.Header, reader); | |||||
return Deserialize(header, reader); | |||||
} | } | ||||
} | } | ||||
@@ -14,15 +14,17 @@ namespace MQTTnet.Server | |||||
{ | { | ||||
public sealed class MqttClientSession | public sealed class MqttClientSession | ||||
{ | { | ||||
private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch(); | |||||
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch(); | |||||
private readonly Stopwatch _lastPacketReceivedTracker = Stopwatch.StartNew(); | |||||
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = Stopwatch.StartNew(); | |||||
private readonly MqttClientSubscriptionsManager _subscriptionsManager; | |||||
private readonly MqttClientSessionsManager _sessionsManager; | |||||
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue; | |||||
private readonly IMqttServerOptions _options; | private readonly IMqttServerOptions _options; | ||||
private readonly IMqttNetLogger _logger; | private readonly IMqttNetLogger _logger; | ||||
private readonly MqttClientSessionsManager _sessionsManager; | |||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager; | |||||
private readonly MqttClientSubscriptionsManager _subscriptionsManager; | |||||
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue; | |||||
private IMqttChannelAdapter _adapter; | private IMqttChannelAdapter _adapter; | ||||
private CancellationTokenSource _cancellationTokenSource; | private CancellationTokenSource _cancellationTokenSource; | ||||
private MqttApplicationMessage _willMessage; | private MqttApplicationMessage _willMessage; | ||||
@@ -30,16 +32,17 @@ namespace MQTTnet.Server | |||||
public MqttClientSession( | public MqttClientSession( | ||||
string clientId, | string clientId, | ||||
IMqttServerOptions options, | IMqttServerOptions options, | ||||
MqttRetainedMessagesManager retainedMessagesManager, | |||||
MqttClientSessionsManager sessionsManager, | MqttClientSessionsManager sessionsManager, | ||||
IMqttNetLogger logger) | IMqttNetLogger logger) | ||||
{ | { | ||||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); | |||||
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); | _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); | ||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||
ClientId = clientId; | ClientId = clientId; | ||||
_options = options; | |||||
_subscriptionsManager = new MqttClientSubscriptionsManager(_options); | _subscriptionsManager = new MqttClientSubscriptionsManager(_options); | ||||
_pendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); | _pendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); | ||||
} | } | ||||
@@ -117,11 +120,11 @@ namespace MQTTnet.Server | |||||
} | } | ||||
} | } | ||||
public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage) | |||||
public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage) | |||||
{ | { | ||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | ||||
var result = _subscriptionsManager.CheckSubscriptions(applicationMessage); | |||||
var result = await _subscriptionsManager.CheckSubscriptionsAsync(applicationMessage); | |||||
if (!result.IsSubscribed) | if (!result.IsSubscribed) | ||||
{ | { | ||||
return; | return; | ||||
@@ -129,6 +132,7 @@ namespace MQTTnet.Server | |||||
var publishPacket = applicationMessage.ToPublishPacket(); | var publishPacket = applicationMessage.ToPublishPacket(); | ||||
publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel; | publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel; | ||||
_pendingMessagesQueue.Enqueue(publishPacket); | _pendingMessagesQueue.Enqueue(publishPacket); | ||||
} | } | ||||
@@ -199,7 +203,7 @@ namespace MQTTnet.Server | |||||
if (packet is MqttUnsubscribePacket unsubscribePacket) | if (packet is MqttUnsubscribePacket unsubscribePacket) | ||||
{ | { | ||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, _subscriptionsManager.Unsubscribe(unsubscribePacket)); | |||||
return HandleIncomingUnsubscribePacketAsync(adapter, unsubscribePacket, cancellationToken); | |||||
} | } | ||||
if (packet is MqttDisconnectPacket || packet is MqttConnectPacket) | if (packet is MqttDisconnectPacket || packet is MqttConnectPacket) | ||||
@@ -213,24 +217,31 @@ namespace MQTTnet.Server | |||||
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) | private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) | ||||
{ | { | ||||
var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket, ClientId); | |||||
var subscribeResult = await _subscriptionsManager.SubscribeAsync(subscribePacket, ClientId); | |||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, subscribeResult.ResponsePacket).ConfigureAwait(false); | await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, subscribeResult.ResponsePacket).ConfigureAwait(false); | ||||
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket).ConfigureAwait(false); | |||||
if (subscribeResult.CloseConnection) | if (subscribeResult.CloseConnection) | ||||
{ | { | ||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttDisconnectPacket()).ConfigureAwait(false); | await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttDisconnectPacket()).ConfigureAwait(false); | ||||
await StopAsync().ConfigureAwait(false); | await StopAsync().ConfigureAwait(false); | ||||
} | } | ||||
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket).ConfigureAwait(false); | |||||
} | |||||
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) | |||||
{ | |||||
var unsubscribeResult = await _subscriptionsManager.UnsubscribeAsync(unsubscribePacket); | |||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, unsubscribeResult); | |||||
} | } | ||||
private async Task EnqueueSubscribedRetainedMessagesAsync(MqttSubscribePacket subscribePacket) | private async Task EnqueueSubscribedRetainedMessagesAsync(MqttSubscribePacket subscribePacket) | ||||
{ | { | ||||
var retainedMessages = await _sessionsManager.GetRetainedMessagesAsync(subscribePacket).ConfigureAwait(false); | |||||
var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(subscribePacket); | |||||
foreach (var publishPacket in retainedMessages) | foreach (var publishPacket in retainedMessages) | ||||
{ | { | ||||
EnqueueApplicationMessage(publishPacket); | |||||
await EnqueueApplicationMessageAsync(publishPacket); | |||||
} | } | ||||
} | } | ||||
@@ -15,23 +15,21 @@ namespace MQTTnet.Server | |||||
public sealed class MqttClientSessionsManager | public sealed class MqttClientSessionsManager | ||||
{ | { | ||||
private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>(); | private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>(); | ||||
private readonly SemaphoreSlim _sessionsSemaphore = new SemaphoreSlim(1, 1); | |||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | |||||
private readonly IMqttServerOptions _options; | private readonly IMqttServerOptions _options; | ||||
private readonly MqttServer _server; | |||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager; | private readonly MqttRetainedMessagesManager _retainedMessagesManager; | ||||
private readonly IMqttNetLogger _logger; | private readonly IMqttNetLogger _logger; | ||||
public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger) | |||||
public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, MqttServer server, IMqttNetLogger logger) | |||||
{ | { | ||||
_server = server ?? throw new ArgumentNullException(nameof(server)); | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||||
_options = options ?? throw new ArgumentNullException(nameof(options)); | _options = options ?? throw new ArgumentNullException(nameof(options)); | ||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); | _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); | ||||
} | } | ||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | |||||
public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected; | |||||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; | |||||
public async Task RunClientSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) | public async Task RunClientSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) | ||||
{ | { | ||||
var clientId = string.Empty; | var clientId = string.Empty; | ||||
@@ -66,11 +64,11 @@ namespace MQTTnet.Server | |||||
IsSessionPresent = clientSession.IsExistingSession | IsSessionPresent = clientSession.IsExistingSession | ||||
}).ConfigureAwait(false); | }).ConfigureAwait(false); | ||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(new ConnectedMqttClient | |||||
_server.OnClientConnected(new ConnectedMqttClient | |||||
{ | { | ||||
ClientId = clientId, | ClientId = clientId, | ||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion | ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion | ||||
})); | |||||
}); | |||||
await clientSession.Session.RunAsync(connectPacket.WillMessage, clientAdapter).ConfigureAwait(false); | await clientSession.Session.RunAsync(connectPacket.WillMessage, clientAdapter).ConfigureAwait(false); | ||||
} | } | ||||
@@ -89,17 +87,17 @@ namespace MQTTnet.Server | |||||
// ignored | // ignored | ||||
} | } | ||||
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(new ConnectedMqttClient | |||||
_server.OnClientDisconnected(new ConnectedMqttClient | |||||
{ | { | ||||
ClientId = clientId, | ClientId = clientId, | ||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion | ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion | ||||
})); | |||||
}); | |||||
} | } | ||||
} | } | ||||
public async Task StopAsync() | public async Task StopAsync() | ||||
{ | { | ||||
await _sessionsSemaphore.WaitAsync().ConfigureAwait(false); | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
try | try | ||||
{ | { | ||||
foreach (var session in _sessions) | foreach (var session in _sessions) | ||||
@@ -111,13 +109,13 @@ namespace MQTTnet.Server | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
_sessionsSemaphore.Release(); | |||||
_semaphore.Release(); | |||||
} | } | ||||
} | } | ||||
public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync() | public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync() | ||||
{ | { | ||||
await _sessionsSemaphore.WaitAsync().ConfigureAwait(false); | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
try | try | ||||
{ | { | ||||
return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient | return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient | ||||
@@ -130,7 +128,7 @@ namespace MQTTnet.Server | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
_sessionsSemaphore.Release(); | |||||
_semaphore.Release(); | |||||
} | } | ||||
} | } | ||||
@@ -138,17 +136,7 @@ namespace MQTTnet.Server | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
if (_options.ApplicationMessageInterceptor != null) | |||||
{ | |||||
var interceptorContext = new MqttApplicationMessageInterceptorContext | |||||
{ | |||||
ApplicationMessage = applicationMessage | |||||
}; | |||||
_options.ApplicationMessageInterceptor(interceptorContext); | |||||
applicationMessage = interceptorContext.ApplicationMessage; | |||||
} | |||||
applicationMessage = InterceptApplicationMessage(applicationMessage); | |||||
if (applicationMessage == null) | if (applicationMessage == null) | ||||
{ | { | ||||
return; | return; | ||||
@@ -159,26 +147,41 @@ namespace MQTTnet.Server | |||||
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false); | await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false); | ||||
} | } | ||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, applicationMessage); | |||||
ApplicationMessageReceived?.Invoke(this, eventArgs); | |||||
_server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage); | |||||
} | } | ||||
catch (Exception exception) | catch (Exception exception) | ||||
{ | { | ||||
_logger.Error<MqttClientSessionsManager>(exception, "Error while processing application message"); | _logger.Error<MqttClientSessionsManager>(exception, "Error while processing application message"); | ||||
} | } | ||||
lock (_sessions) | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
try | |||||
{ | { | ||||
foreach (var clientSession in _sessions.Values) | foreach (var clientSession in _sessions.Values) | ||||
{ | { | ||||
clientSession.EnqueueApplicationMessage(applicationMessage); | |||||
await clientSession.EnqueueApplicationMessageAsync(applicationMessage); | |||||
} | } | ||||
} | } | ||||
finally | |||||
{ | |||||
_semaphore.Release(); | |||||
} | |||||
} | } | ||||
public Task<List<MqttApplicationMessage>> GetRetainedMessagesAsync(MqttSubscribePacket subscribePacket) | |||||
private MqttApplicationMessage InterceptApplicationMessage(MqttApplicationMessage applicationMessage) | |||||
{ | { | ||||
return _retainedMessagesManager.GetSubscribedMessagesAsync(subscribePacket); | |||||
if (_options.ApplicationMessageInterceptor == null) | |||||
{ | |||||
return applicationMessage; | |||||
} | |||||
var interceptorContext = new MqttApplicationMessageInterceptorContext | |||||
{ | |||||
ApplicationMessage = applicationMessage | |||||
}; | |||||
_options.ApplicationMessageInterceptor(interceptorContext); | |||||
return interceptorContext.ApplicationMessage; | |||||
} | } | ||||
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) | private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) | ||||
@@ -200,7 +203,7 @@ namespace MQTTnet.Server | |||||
private async Task<GetOrCreateClientSessionResult> GetOrCreateClientSessionAsync(MqttConnectPacket connectPacket) | private async Task<GetOrCreateClientSessionResult> GetOrCreateClientSessionAsync(MqttConnectPacket connectPacket) | ||||
{ | { | ||||
await _sessionsSemaphore.WaitAsync().ConfigureAwait(false); | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
try | try | ||||
{ | { | ||||
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); | var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); | ||||
@@ -225,7 +228,7 @@ namespace MQTTnet.Server | |||||
{ | { | ||||
isExistingSession = false; | isExistingSession = false; | ||||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _logger); | |||||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, _retainedMessagesManager, this, _logger); | |||||
_sessions[connectPacket.ClientId] = clientSession; | _sessions[connectPacket.ClientId] = clientSession; | ||||
_logger.Trace<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId); | _logger.Trace<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId); | ||||
@@ -235,7 +238,7 @@ namespace MQTTnet.Server | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
_sessionsSemaphore.Release(); | |||||
_semaphore.Release(); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -1,5 +1,8 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using MQTTnet.Packets; | using MQTTnet.Packets; | ||||
using MQTTnet.Protocol; | using MQTTnet.Protocol; | ||||
@@ -7,6 +10,7 @@ namespace MQTTnet.Server | |||||
{ | { | ||||
public sealed class MqttClientSubscriptionsManager | public sealed class MqttClientSubscriptionsManager | ||||
{ | { | ||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | |||||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>(); | private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>(); | ||||
private readonly IMqttServerOptions _options; | private readonly IMqttServerOptions _options; | ||||
@@ -15,24 +19,34 @@ namespace MQTTnet.Server | |||||
_options = options ?? throw new ArgumentNullException(nameof(options)); | _options = options ?? throw new ArgumentNullException(nameof(options)); | ||||
} | } | ||||
public MqttClientSubscribeResult Subscribe(MqttSubscribePacket subscribePacket, string clientId) | |||||
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttSubscribePacket subscribePacket, string clientId) | |||||
{ | { | ||||
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); | if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); | ||||
var responsePacket = subscribePacket.CreateResponse<MqttSubAckPacket>(); | |||||
var closeConnection = false; | |||||
var result = new MqttClientSubscribeResult | |||||
{ | |||||
ResponsePacket = subscribePacket.CreateResponse<MqttSubAckPacket>(), | |||||
CloseConnection = false | |||||
}; | |||||
lock (_subscriptions) | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
try | |||||
{ | { | ||||
foreach (var topicFilter in subscribePacket.TopicFilters) | foreach (var topicFilter in subscribePacket.TopicFilters) | ||||
{ | { | ||||
var interceptorContext = new MqttSubscriptionInterceptorContext(clientId, topicFilter); | |||||
_options.SubscriptionInterceptor?.Invoke(interceptorContext); | |||||
responsePacket.SubscribeReturnCodes.Add(interceptorContext.AcceptSubscription ? MqttSubscribeReturnCode.SuccessMaximumQoS1 : MqttSubscribeReturnCode.Failure); | |||||
var interceptorContext = InterceptSubscribe(clientId, topicFilter); | |||||
if (!interceptorContext.AcceptSubscription) | |||||
{ | |||||
result.ResponsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure); | |||||
} | |||||
else | |||||
{ | |||||
result.ResponsePacket.SubscribeReturnCodes.Add(ConvertToMaximumQoS(topicFilter.QualityOfServiceLevel)); | |||||
} | |||||
if (interceptorContext.CloseConnection) | if (interceptorContext.CloseConnection) | ||||
{ | { | ||||
closeConnection = true; | |||||
result.CloseConnection = true; | |||||
} | } | ||||
if (interceptorContext.AcceptSubscription) | if (interceptorContext.AcceptSubscription) | ||||
@@ -41,35 +55,42 @@ namespace MQTTnet.Server | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return new MqttClientSubscribeResult | |||||
finally | |||||
{ | { | ||||
ResponsePacket = responsePacket, | |||||
CloseConnection = closeConnection | |||||
}; | |||||
_semaphore.Release(); | |||||
} | |||||
return result; | |||||
} | } | ||||
public MqttUnsubAckPacket Unsubscribe(MqttUnsubscribePacket unsubscribePacket) | |||||
public async Task<MqttUnsubAckPacket> UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket) | |||||
{ | { | ||||
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); | if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); | ||||
lock (_subscriptions) | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
try | |||||
{ | { | ||||
foreach (var topicFilter in unsubscribePacket.TopicFilters) | foreach (var topicFilter in unsubscribePacket.TopicFilters) | ||||
{ | { | ||||
_subscriptions.Remove(topicFilter); | _subscriptions.Remove(topicFilter); | ||||
} | } | ||||
} | } | ||||
finally | |||||
{ | |||||
_semaphore.Release(); | |||||
} | |||||
return unsubscribePacket.CreateResponse<MqttUnsubAckPacket>(); | return unsubscribePacket.CreateResponse<MqttUnsubAckPacket>(); | ||||
} | } | ||||
public CheckSubscriptionsResult CheckSubscriptions(MqttApplicationMessage applicationMessage) | |||||
public async Task<CheckSubscriptionsResult> CheckSubscriptionsAsync(MqttApplicationMessage applicationMessage) | |||||
{ | { | ||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | ||||
lock (_subscriptions) | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
try | |||||
{ | { | ||||
var qosLevels = new HashSet<MqttQualityOfServiceLevel>(); | |||||
foreach (var subscription in _subscriptions) | foreach (var subscription in _subscriptions) | ||||
{ | { | ||||
if (!MqttTopicFilterComparer.IsMatch(applicationMessage.Topic, subscription.Key)) | if (!MqttTopicFilterComparer.IsMatch(applicationMessage.Topic, subscription.Key)) | ||||
@@ -77,24 +98,64 @@ namespace MQTTnet.Server | |||||
continue; | continue; | ||||
} | } | ||||
var effectiveQos = subscription.Value; | |||||
if (applicationMessage.QualityOfServiceLevel < effectiveQos) | |||||
{ | |||||
effectiveQos = applicationMessage.QualityOfServiceLevel; | |||||
} | |||||
qosLevels.Add(subscription.Value); | |||||
} | |||||
if (qosLevels.Count == 0) | |||||
{ | |||||
return new CheckSubscriptionsResult | return new CheckSubscriptionsResult | ||||
{ | { | ||||
IsSubscribed = true, | |||||
QualityOfServiceLevel = effectiveQos | |||||
IsSubscribed = false | |||||
}; | }; | ||||
} | } | ||||
return CreateSubscriptionResult(applicationMessage, qosLevels); | |||||
} | |||||
finally | |||||
{ | |||||
_semaphore.Release(); | |||||
} | |||||
} | |||||
private MqttSubscriptionInterceptorContext InterceptSubscribe(string clientId, TopicFilter topicFilter) | |||||
{ | |||||
var interceptorContext = new MqttSubscriptionInterceptorContext(clientId, topicFilter); | |||||
_options.SubscriptionInterceptor?.Invoke(interceptorContext); | |||||
return interceptorContext; | |||||
} | |||||
private static CheckSubscriptionsResult CreateSubscriptionResult(MqttApplicationMessage applicationMessage, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels) | |||||
{ | |||||
MqttQualityOfServiceLevel effectiveQoS; | |||||
if (subscribedQoSLevels.Contains(applicationMessage.QualityOfServiceLevel)) | |||||
{ | |||||
effectiveQoS = applicationMessage.QualityOfServiceLevel; | |||||
} | |||||
else if (subscribedQoSLevels.Count == 1) | |||||
{ | |||||
effectiveQoS = subscribedQoSLevels.First(); | |||||
} | |||||
else | |||||
{ | |||||
effectiveQoS = subscribedQoSLevels.Max(); | |||||
} | } | ||||
return new CheckSubscriptionsResult | return new CheckSubscriptionsResult | ||||
{ | { | ||||
IsSubscribed = false | |||||
IsSubscribed = true, | |||||
QualityOfServiceLevel = effectiveQoS | |||||
}; | }; | ||||
} | } | ||||
private static MqttSubscribeReturnCode ConvertToMaximumQoS(MqttQualityOfServiceLevel qualityOfServiceLevel) | |||||
{ | |||||
switch (qualityOfServiceLevel) | |||||
{ | |||||
case MqttQualityOfServiceLevel.AtMostOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS0; | |||||
case MqttQualityOfServiceLevel.AtLeastOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS1; | |||||
case MqttQualityOfServiceLevel.ExactlyOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS2; | |||||
default: return MqttSubscribeReturnCode.Failure; | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
@@ -11,7 +11,7 @@ namespace MQTTnet.Server | |||||
public sealed class MqttRetainedMessagesManager | public sealed class MqttRetainedMessagesManager | ||||
{ | { | ||||
private readonly Dictionary<string, MqttApplicationMessage> _retainedMessages = new Dictionary<string, MqttApplicationMessage>(); | private readonly Dictionary<string, MqttApplicationMessage> _retainedMessages = new Dictionary<string, MqttApplicationMessage>(); | ||||
private readonly SemaphoreSlim _gate = new SemaphoreSlim(1, 1); | |||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | |||||
private readonly IMqttNetLogger _logger; | private readonly IMqttNetLogger _logger; | ||||
private readonly IMqttServerOptions _options; | private readonly IMqttServerOptions _options; | ||||
@@ -28,7 +28,7 @@ namespace MQTTnet.Server | |||||
return; | return; | ||||
} | } | ||||
await _gate.WaitAsync(); | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
try | try | ||||
{ | { | ||||
var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync(); | var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync(); | ||||
@@ -45,7 +45,7 @@ namespace MQTTnet.Server | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
_gate.Release(); | |||||
_semaphore.Release(); | |||||
} | } | ||||
} | } | ||||
@@ -53,7 +53,7 @@ namespace MQTTnet.Server | |||||
{ | { | ||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | ||||
await _gate.WaitAsync().ConfigureAwait(false); | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
try | try | ||||
{ | { | ||||
await HandleMessageInternalAsync(clientId, applicationMessage); | await HandleMessageInternalAsync(clientId, applicationMessage); | ||||
@@ -64,7 +64,7 @@ namespace MQTTnet.Server | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
_gate.Release(); | |||||
_semaphore.Release(); | |||||
} | } | ||||
} | } | ||||
@@ -72,7 +72,7 @@ namespace MQTTnet.Server | |||||
{ | { | ||||
var retainedMessages = new List<MqttApplicationMessage>(); | var retainedMessages = new List<MqttApplicationMessage>(); | ||||
await _gate.WaitAsync().ConfigureAwait(false); | |||||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
try | try | ||||
{ | { | ||||
foreach (var retainedMessage in _retainedMessages.Values) | foreach (var retainedMessage in _retainedMessages.Values) | ||||
@@ -96,7 +96,7 @@ namespace MQTTnet.Server | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
_gate.Release(); | |||||
_semaphore.Release(); | |||||
} | } | ||||
return retainedMessages; | return retainedMessages; | ||||
@@ -60,11 +60,7 @@ namespace MQTTnet.Server | |||||
_cancellationTokenSource = new CancellationTokenSource(); | _cancellationTokenSource = new CancellationTokenSource(); | ||||
_retainedMessagesManager = new MqttRetainedMessagesManager(_options, _logger); | _retainedMessagesManager = new MqttRetainedMessagesManager(_options, _logger); | ||||
_clientSessionsManager = new MqttClientSessionsManager(_options, _retainedMessagesManager, _logger); | |||||
_clientSessionsManager.ApplicationMessageReceived += OnApplicationMessageReceived; | |||||
_clientSessionsManager.ClientConnected += OnClientConnected; | |||||
_clientSessionsManager.ClientDisconnected += OnClientDisconnected; | |||||
_clientSessionsManager = new MqttClientSessionsManager(_options, _retainedMessagesManager, this, _logger); | |||||
await _retainedMessagesManager.LoadMessagesAsync(); | await _retainedMessagesManager.LoadMessagesAsync(); | ||||
@@ -104,40 +100,39 @@ namespace MQTTnet.Server | |||||
finally | finally | ||||
{ | { | ||||
_cancellationTokenSource = null; | _cancellationTokenSource = null; | ||||
_retainedMessagesManager = null; | _retainedMessagesManager = null; | ||||
if (_clientSessionsManager != null) | |||||
{ | |||||
_clientSessionsManager.ApplicationMessageReceived -= OnApplicationMessageReceived; | |||||
_clientSessionsManager.ClientConnected -= OnClientConnected; | |||||
_clientSessionsManager.ClientDisconnected -= OnClientDisconnected; | |||||
} | |||||
_clientSessionsManager = null; | _clientSessionsManager = null; | ||||
} | } | ||||
} | } | ||||
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) | |||||
internal void OnClientConnected(ConnectedMqttClient client) | |||||
{ | { | ||||
eventArgs.SessionTask = Task.Run(async () => await _clientSessionsManager.RunClientSessionAsync(eventArgs.Client, _cancellationTokenSource.Token), _cancellationTokenSource.Token); | |||||
if (client == null) throw new ArgumentNullException(nameof(client)); | |||||
_logger.Info<MqttServer>("Client '{0}': Connected.", client.ClientId); | |||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client)); | |||||
} | } | ||||
private void OnClientConnected(object sender, MqttClientConnectedEventArgs eventArgs) | |||||
internal void OnClientDisconnected(ConnectedMqttClient client) | |||||
{ | { | ||||
_logger.Info<MqttServer>("Client '{0}': Connected.", eventArgs.Client.ClientId); | |||||
ClientConnected?.Invoke(this, eventArgs); | |||||
if (client == null) throw new ArgumentNullException(nameof(client)); | |||||
_logger.Info<MqttServer>("Client '{0}': Disconnected.", client.ClientId); | |||||
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client)); | |||||
} | } | ||||
private void OnClientDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs) | |||||
internal void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage) | |||||
{ | { | ||||
_logger.Info<MqttServer>("Client '{0}': Disconnected.", eventArgs.Client.ClientId); | |||||
ClientDisconnected?.Invoke(this, eventArgs); | |||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | |||||
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage)); | |||||
} | } | ||||
private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e) | |||||
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) | |||||
{ | { | ||||
ApplicationMessageReceived?.Invoke(this, e); | |||||
eventArgs.SessionTask = Task.Run( | |||||
async () => await _clientSessionsManager.RunClientSessionAsync(eventArgs.Client, _cancellationTokenSource.Token).ConfigureAwait(false), | |||||
_cancellationTokenSource.Token); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -1,17 +1,19 @@ | |||||
namespace MQTTnet.Server | |||||
using System; | |||||
namespace MQTTnet.Server | |||||
{ | { | ||||
public class MqttSubscriptionInterceptorContext | public class MqttSubscriptionInterceptorContext | ||||
{ | { | ||||
public MqttSubscriptionInterceptorContext(string clientId, TopicFilter topicFilter) | public MqttSubscriptionInterceptorContext(string clientId, TopicFilter topicFilter) | ||||
{ | { | ||||
ClientId = clientId; | ClientId = clientId; | ||||
TopicFilter = topicFilter; | |||||
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); | |||||
} | } | ||||
public string ClientId { get; } | public string ClientId { get; } | ||||
public TopicFilter TopicFilter { get; } | public TopicFilter TopicFilter { get; } | ||||
public bool AcceptSubscription { get; set; } = true; | public bool AcceptSubscription { get; set; } = true; | ||||
public bool CloseConnection { get; set; } | public bool CloseConnection { get; set; } | ||||
@@ -4,7 +4,7 @@ namespace MQTTnet | |||||
{ | { | ||||
public sealed class TopicFilter | public sealed class TopicFilter | ||||
{ | { | ||||
public TopicFilter(string topic, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce) | |||||
public TopicFilter(string topic, MqttQualityOfServiceLevel qualityOfServiceLevel) | |||||
{ | { | ||||
Topic = topic; | Topic = topic; | ||||
QualityOfServiceLevel = qualityOfServiceLevel; | QualityOfServiceLevel = qualityOfServiceLevel; | ||||
@@ -1,7 +1,7 @@ | |||||
| | ||||
Microsoft Visual Studio Solution File, Format Version 12.00 | Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
# Visual Studio 15 | # Visual Studio 15 | ||||
VisualStudioVersion = 15.0.27004.2009 | |||||
VisualStudioVersion = 15.0.27004.2010 | |||||
MinimumVisualStudioVersion = 10.0.40219.1 | 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}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}" | ||||
EndProject | EndProject | ||||
@@ -33,6 +33,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.AspNetCore2 | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Frameworks\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Frameworks\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}" | ||||
EndProject | EndProject | ||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{12816BCC-AF9E-44A9-9AE5-C246AF2A0587}" | |||||
EndProject | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}" | |||||
EndProject | |||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
Debug|Any CPU = Debug|Any CPU | Debug|Any CPU = Debug|Any CPU | ||||
@@ -147,6 +151,22 @@ Global | |||||
{F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|x64.Build.0 = 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.ActiveCfg = Release|Any CPU | ||||
{F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|x86.Build.0 = Release|Any CPU | {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|x86.Build.0 = Release|Any CPU | ||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|ARM.ActiveCfg = Debug|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|ARM.Build.0 = Debug|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|x64.ActiveCfg = Debug|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|x64.Build.0 = Debug|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|x86.ActiveCfg = Debug|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|x86.Build.0 = Debug|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|ARM.ActiveCfg = Release|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|ARM.Build.0 = Release|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x64.ActiveCfg = Release|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x64.Build.0 = Release|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x86.ActiveCfg = Release|Any CPU | |||||
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x86.Build.0 = Release|Any CPU | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
@@ -158,6 +178,7 @@ Global | |||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} | {3D283AAD-AAA8-4339-8394-52F80B6304DB} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} | ||||
{C6FF8AEA-0855-41EC-A1F3-AC262225BAB9} = {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} | {F10C4060-F7EE-4A83-919F-FF723E72F94A} = {32A630A7-2598-41D7-B625-204CD906F5FB} | ||||
{C444E9C8-95FA-430E-9126-274129DE16CD} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587} | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(ExtensibilityGlobals) = postSolution | GlobalSection(ExtensibilityGlobals) = postSolution | ||||
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} | SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} | ||||
@@ -4,7 +4,6 @@ using System.IO; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading; | using System.Threading; | ||||
using Microsoft.VisualStudio.TestTools.UnitTesting; | using Microsoft.VisualStudio.TestTools.UnitTesting; | ||||
using MQTTnet.Adapter; | |||||
using MQTTnet.Packets; | using MQTTnet.Packets; | ||||
using MQTTnet.Protocol; | using MQTTnet.Protocol; | ||||
using MQTTnet.Serializer; | using MQTTnet.Serializer; | ||||
@@ -410,7 +409,7 @@ namespace MQTTnet.Core.Tests | |||||
using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.BodyLength)) | using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.BodyLength)) | ||||
{ | { | ||||
var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header, bodyStream)); | |||||
var deserializedPacket = serializer.Deserialize(header, bodyStream.ToArray()); | |||||
var buffer2 = serializer.Serialize(deserializedPacket); | var buffer2 = serializer.Serialize(deserializedPacket); | ||||
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2))); | Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2))); | ||||
@@ -62,7 +62,7 @@ namespace MQTTnet.Core.Tests | |||||
var c2 = await serverAdapter.ConnectTestClient(s, "c2", willMessage); | var c2 = await serverAdapter.ConnectTestClient(s, "c2", willMessage); | ||||
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; | c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; | ||||
await c1.SubscribeAsync(new TopicFilter("#")); | |||||
await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build()); | |||||
await c2.DisconnectAsync(); | await c2.DisconnectAsync(); | ||||
@@ -167,7 +167,7 @@ namespace MQTTnet.Core.Tests | |||||
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); | var c2 = await serverAdapter.ConnectTestClient(s, "c2"); | ||||
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; | c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; | ||||
await c2.SubscribeAsync(new TopicFilter("retained")); | |||||
await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); | |||||
await Task.Delay(500); | await Task.Delay(500); | ||||
} | } | ||||
@@ -199,7 +199,7 @@ namespace MQTTnet.Core.Tests | |||||
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); | var c2 = await serverAdapter.ConnectTestClient(s, "c2"); | ||||
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; | c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; | ||||
await c2.SubscribeAsync(new TopicFilter("retained")); | |||||
await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); | |||||
await Task.Delay(500); | await Task.Delay(500); | ||||
} | } | ||||
@@ -277,7 +277,7 @@ namespace MQTTnet.Core.Tests | |||||
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); | var c2 = await serverAdapter.ConnectTestClient(s, "c2"); | ||||
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; | c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; | ||||
await c2.SubscribeAsync(new TopicFilter("retained")); | |||||
await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); | |||||
await Task.Delay(500); | await Task.Delay(500); | ||||
} | } | ||||
@@ -14,9 +14,9 @@ namespace MQTTnet.Core.Tests | |||||
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); | var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); | ||||
var sp = new MqttSubscribePacket(); | var sp = new MqttSubscribePacket(); | ||||
sp.TopicFilters.Add(new TopicFilter("A/B/C")); | |||||
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); | |||||
sm.Subscribe(sp, ""); | |||||
sm.SubscribeAsync(sp, "").Wait(); | |||||
var pp = new MqttApplicationMessage | var pp = new MqttApplicationMessage | ||||
{ | { | ||||
@@ -24,7 +24,52 @@ namespace MQTTnet.Core.Tests | |||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | ||||
}; | }; | ||||
Assert.IsTrue(sm.CheckSubscriptions(pp).IsSubscribed); | |||||
var result = sm.CheckSubscriptionsAsync(pp).Result; | |||||
Assert.IsTrue(result.IsSubscribed); | |||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); | |||||
} | |||||
[TestMethod] | |||||
public void MqttSubscriptionsManager_SubscribeDifferentQoSSuccess() | |||||
{ | |||||
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); | |||||
var sp = new MqttSubscribePacket(); | |||||
sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtMostOnce)); | |||||
sm.SubscribeAsync(sp, "").Wait(); | |||||
var pp = new MqttApplicationMessage | |||||
{ | |||||
Topic = "A/B/C", | |||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce | |||||
}; | |||||
var result = sm.CheckSubscriptionsAsync(pp).Result; | |||||
Assert.IsTrue(result.IsSubscribed); | |||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); | |||||
} | |||||
[TestMethod] | |||||
public void MqttSubscriptionsManager_SubscribeTwoTimesSuccess() | |||||
{ | |||||
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); | |||||
var sp = new MqttSubscribePacket(); | |||||
sp.TopicFilters.Add(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)); | |||||
sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtLeastOnce)); | |||||
sm.SubscribeAsync(sp, "").Wait(); | |||||
var pp = new MqttApplicationMessage | |||||
{ | |||||
Topic = "A/B/C", | |||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce | |||||
}; | |||||
var result = sm.CheckSubscriptionsAsync(pp).Result; | |||||
Assert.IsTrue(result.IsSubscribed); | |||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce); | |||||
} | } | ||||
[TestMethod] | [TestMethod] | ||||
@@ -33,9 +78,9 @@ namespace MQTTnet.Core.Tests | |||||
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); | var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); | ||||
var sp = new MqttSubscribePacket(); | var sp = new MqttSubscribePacket(); | ||||
sp.TopicFilters.Add(new TopicFilter("A/B/C")); | |||||
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); | |||||
sm.Subscribe(sp, ""); | |||||
sm.SubscribeAsync(sp, "").Wait(); | |||||
var pp = new MqttApplicationMessage | var pp = new MqttApplicationMessage | ||||
{ | { | ||||
@@ -43,7 +88,7 @@ namespace MQTTnet.Core.Tests | |||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | ||||
}; | }; | ||||
Assert.IsFalse(sm.CheckSubscriptions(pp).IsSubscribed); | |||||
Assert.IsFalse(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed); | |||||
} | } | ||||
[TestMethod] | [TestMethod] | ||||
@@ -52,9 +97,9 @@ namespace MQTTnet.Core.Tests | |||||
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); | var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); | ||||
var sp = new MqttSubscribePacket(); | var sp = new MqttSubscribePacket(); | ||||
sp.TopicFilters.Add(new TopicFilter("A/B/C")); | |||||
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); | |||||
sm.Subscribe(sp, ""); | |||||
sm.SubscribeAsync(sp, "").Wait(); | |||||
var pp = new MqttApplicationMessage | var pp = new MqttApplicationMessage | ||||
{ | { | ||||
@@ -62,13 +107,13 @@ namespace MQTTnet.Core.Tests | |||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | ||||
}; | }; | ||||
Assert.IsTrue(sm.CheckSubscriptions(pp).IsSubscribed); | |||||
Assert.IsTrue(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed); | |||||
var up = new MqttUnsubscribePacket(); | var up = new MqttUnsubscribePacket(); | ||||
up.TopicFilters.Add("A/B/C"); | up.TopicFilters.Add("A/B/C"); | ||||
sm.Unsubscribe(up); | |||||
sm.UnsubscribeAsync(up).Wait(); | |||||
Assert.IsFalse(sm.CheckSubscriptions(pp).IsSubscribed); | |||||
Assert.IsFalse(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -2,6 +2,7 @@ | |||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using MQTTnet.Client; | using MQTTnet.Client; | ||||
using MQTTnet.Protocol; | |||||
namespace MQTTnet.TestApp.NetCore | namespace MQTTnet.TestApp.NetCore | ||||
{ | { | ||||
@@ -17,7 +18,8 @@ namespace MQTTnet.TestApp.NetCore | |||||
CleanSession = true, | CleanSession = true, | ||||
ChannelOptions = new MqttClientTcpOptions | ChannelOptions = new MqttClientTcpOptions | ||||
{ | { | ||||
Server = "localhost" | |||||
//Server = "localhost", | |||||
Server = "192.168.1.174" | |||||
}, | }, | ||||
//ChannelOptions = new MqttClientWebSocketOptions | //ChannelOptions = new MqttClientWebSocketOptions | ||||
//{ | //{ | ||||
@@ -78,6 +80,8 @@ namespace MQTTnet.TestApp.NetCore | |||||
{ | { | ||||
Console.ReadLine(); | Console.ReadLine(); | ||||
await client.SubscribeAsync(new TopicFilter("test", MqttQualityOfServiceLevel.AtMostOnce)); | |||||
var applicationMessage = new MqttApplicationMessageBuilder() | var applicationMessage = new MqttApplicationMessageBuilder() | ||||
.WithTopic("A/B/C") | .WithTopic("A/B/C") | ||||
.WithPayload("Hello World") | .WithPayload("Hello World") | ||||
@@ -6,6 +6,10 @@ | |||||
<TargetFrameworks>netcoreapp2.0;net452;net461</TargetFrameworks> | <TargetFrameworks>netcoreapp2.0;net452;net461</TargetFrameworks> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netcoreapp2.0|AnyCPU'"> | |||||
<DefineConstants>RELEASE;NETCOREAPP2_0</DefineConstants> | |||||
</PropertyGroup> | |||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> | <PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
@@ -1,5 +1,4 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | |||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Text; | using System.Text; | ||||
@@ -64,9 +63,17 @@ namespace MQTTnet.TestApp.NetCore | |||||
sentMessagesCount++; | sentMessagesCount++; | ||||
} | } | ||||
Console.WriteLine($"Sending {sentMessagesCount} messages per second."); | |||||
Console.WriteLine($"Sending {sentMessagesCount} messages per second. #1"); | |||||
sentMessagesCount = 0; | |||||
stopwatch.Restart(); | stopwatch.Restart(); | ||||
while (stopwatch.ElapsedMilliseconds < 1000) | |||||
{ | |||||
await client.PublishAsync(messages).ConfigureAwait(false); | |||||
sentMessagesCount++; | |||||
} | |||||
Console.WriteLine($"Sending {sentMessagesCount} messages per second. #2"); | |||||
var testMessageCount = 10000; | var testMessageCount = 10000; | ||||
for (var i = 0; i < testMessageCount; i++) | for (var i = 0; i < testMessageCount; i++) | ||||
@@ -141,8 +148,8 @@ namespace MQTTnet.TestApp.NetCore | |||||
{ | { | ||||
var mqttServer = new MqttFactory().CreateMqttServer(); | var mqttServer = new MqttFactory().CreateMqttServer(); | ||||
var msgs = 0; | |||||
var stopwatch = Stopwatch.StartNew(); | |||||
////var msgs = 0; | |||||
////var stopwatch = Stopwatch.StartNew(); | |||||
////mqttServer.ApplicationMessageReceived += (sender, args) => | ////mqttServer.ApplicationMessageReceived += (sender, args) => | ||||
////{ | ////{ | ||||
//// msgs++; | //// msgs++; | ||||
@@ -29,6 +29,7 @@ namespace MQTTnet.TestApp.NetCore | |||||
}, | }, | ||||
Storage = new RetainedMessageHandler(), | Storage = new RetainedMessageHandler(), | ||||
ApplicationMessageInterceptor = context => | ApplicationMessageInterceptor = context => | ||||
{ | { | ||||
if (MqttTopicFilterComparer.IsMatch(context.ApplicationMessage.Topic, "/myTopic/WithTimestamp/#")) | if (MqttTopicFilterComparer.IsMatch(context.ApplicationMessage.Topic, "/myTopic/WithTimestamp/#")) | ||||
@@ -67,18 +68,29 @@ namespace MQTTnet.TestApp.NetCore | |||||
mqttServer.ApplicationMessageReceived += (s, e) => | mqttServer.ApplicationMessageReceived += (s, e) => | ||||
{ | { | ||||
MqttNetConsoleLogger.PrintToConsole( | MqttNetConsoleLogger.PrintToConsole( | ||||
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}'", | |||||
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'", | |||||
ConsoleColor.Magenta); | ConsoleColor.Magenta); | ||||
}; | }; | ||||
options.ApplicationMessageInterceptor = c => | options.ApplicationMessageInterceptor = c => | ||||
{ | { | ||||
var content = JObject.Parse(Encoding.UTF8.GetString(c.ApplicationMessage.Payload)); | |||||
var timestampProperty = content.Property("timestamp"); | |||||
if (timestampProperty != null && timestampProperty.Value.Type == JTokenType.Null) | |||||
if (c.ApplicationMessage.Payload == null || c.ApplicationMessage.Payload.Length == 0) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
var content = JObject.Parse(Encoding.UTF8.GetString(c.ApplicationMessage.Payload)); | |||||
var timestampProperty = content.Property("timestamp"); | |||||
if (timestampProperty != null && timestampProperty.Value.Type == JTokenType.Null) | |||||
{ | |||||
timestampProperty.Value = DateTime.Now.ToString("O"); | |||||
c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString()); | |||||
} | |||||
} | |||||
catch (Exception e) | |||||
{ | { | ||||
timestampProperty.Value = DateTime.Now.ToString("O"); | |||||
c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString()); | |||||
} | } | ||||
}; | }; | ||||
@@ -127,6 +127,10 @@ | |||||
</Page> | </Page> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<ProjectReference Include="..\..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj"> | |||||
<Project>{c444e9c8-95fa-430e-9126-274129de16cd}</Project> | |||||
<Name>MQTTnet.Extensions.Rpc</Name> | |||||
</ProjectReference> | |||||
<ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj"> | <ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj"> | ||||
<Project>{3587e506-55a2-4eb3-99c7-dc01e42d25d2}</Project> | <Project>{3587e506-55a2-4eb3-99c7-dc01e42d25d2}</Project> | ||||
<Name>MQTTnet.NetStandard</Name> | <Name>MQTTnet.NetStandard</Name> | ||||
@@ -65,6 +65,40 @@ | |||||
<Button Click="Publish" Width="120">Publish</Button> | <Button Click="Publish" Width="120">Publish</Button> | ||||
</StackPanel> | </StackPanel> | ||||
</PivotItem> | </PivotItem> | ||||
<PivotItem Header="Execute RPC"> | |||||
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> | |||||
<TextBlock>Method:</TextBlock> | |||||
<TextBox x:Name="RpcMethod"></TextBox> | |||||
<TextBlock>Payload:</TextBlock> | |||||
<TextBox x:Name="RpcPayload"></TextBox> | |||||
<StackPanel Orientation="Horizontal"> | |||||
<RadioButton x:Name="RpcText" IsChecked="True" GroupName="payload">Text</RadioButton> | |||||
<RadioButton x:Name="RpcBase64" GroupName="payload">Base64</RadioButton> | |||||
</StackPanel> | |||||
<TextBlock>QoS:</TextBlock> | |||||
<StackPanel Orientation="Horizontal"> | |||||
<RadioButton Margin="0,0,10,0" x:Name="RpcQoS0" GroupName="qos" IsChecked="True">0 (At most once)</RadioButton> | |||||
<RadioButton Margin="0,0,10,0" x:Name="RpcQoS1" GroupName="qos">1 (At least once)</RadioButton> | |||||
<RadioButton Margin="0,0,10,0" x:Name="RpcQoS2" GroupName="qos">2 (Exactly once)</RadioButton> | |||||
</StackPanel> | |||||
<TextBlock>Responses:</TextBlock> | |||||
<ListBox MinHeight="50" MaxHeight="250" x:Name="RpcResponses" Margin="0,0,0,10"> | |||||
<ListBox.ItemTemplate> | |||||
<DataTemplate> | |||||
<ContentPresenter Content="{Binding}" FontFamily="Consolas" FontSize="12"></ContentPresenter> | |||||
</DataTemplate> | |||||
</ListBox.ItemTemplate> | |||||
</ListBox> | |||||
<StackPanel Orientation="Horizontal"> | |||||
<Button Click="ExecuteRpc" Width="120" Margin="0,0,10,0">Execute</Button> | |||||
<Button Click="ClearRpcResponses" Width="200">Clear responses</Button> | |||||
</StackPanel> | |||||
</StackPanel> | |||||
</PivotItem> | |||||
<PivotItem Header="Subscribe"> | <PivotItem Header="Subscribe"> | ||||
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> | <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> | ||||
<TextBlock>Topic:</TextBlock> | <TextBlock>Topic:</TextBlock> | ||||
@@ -86,7 +120,7 @@ | |||||
</DataTemplate> | </DataTemplate> | ||||
</ListBox.ItemTemplate> | </ListBox.ItemTemplate> | ||||
</ListBox> | </ListBox> | ||||
<StackPanel Orientation="Horizontal"> | <StackPanel Orientation="Horizontal"> | ||||
<Button Click="Subscribe" Width="120" Margin="0,0,10,0">Subscribe</Button> | <Button Click="Subscribe" Width="120" Margin="0,0,10,0">Subscribe</Button> | ||||
<Button Click="Unsubscribe" Width="120" Margin="0,0,10,0">Unsubscribe</Button> | <Button Click="Unsubscribe" Width="120" Margin="0,0,10,0">Unsubscribe</Button> | ||||
@@ -101,7 +135,7 @@ | |||||
<CheckBox x:Name="ServerPersistRetainedMessages" IsChecked="True">Persist retained messages in JSON format</CheckBox> | <CheckBox x:Name="ServerPersistRetainedMessages" IsChecked="True">Persist retained messages in JSON format</CheckBox> | ||||
<CheckBox x:Name="ServerClearRetainedMessages">Clear previously retained messages on startup</CheckBox> | <CheckBox x:Name="ServerClearRetainedMessages">Clear previously retained messages on startup</CheckBox> | ||||
<StackPanel Orientation="Horizontal"> | <StackPanel Orientation="Horizontal"> | ||||
<Button Width="120" Margin="0,0,10,0" Click="StartServer">Start</Button> | <Button Width="120" Margin="0,0,10,0" Click="StartServer">Start</Button> | ||||
<Button Width="120" Margin="0,0,10,0" Click="StopServer">Stop</Button> | <Button Width="120" Margin="0,0,10,0" Click="StopServer">Stop</Button> | ||||
@@ -7,6 +7,8 @@ using Windows.UI.Core; | |||||
using Windows.UI.Xaml; | using Windows.UI.Xaml; | ||||
using MQTTnet.Client; | using MQTTnet.Client; | ||||
using MQTTnet.Diagnostics; | using MQTTnet.Diagnostics; | ||||
using MQTTnet.Exceptions; | |||||
using MQTTnet.Extensions.Rpc; | |||||
using MQTTnet.Implementations; | using MQTTnet.Implementations; | ||||
using MQTTnet.ManagedClient; | using MQTTnet.ManagedClient; | ||||
using MQTTnet.Protocol; | using MQTTnet.Protocol; | ||||
@@ -245,6 +247,95 @@ namespace MQTTnet.TestApp.UniversalWindows | |||||
// This code is for the Wiki at GitHub! | // This code is for the Wiki at GitHub! | ||||
// ReSharper disable once UnusedMember.Local | // ReSharper disable once UnusedMember.Local | ||||
private async void StartServer(object sender, RoutedEventArgs e) | |||||
{ | |||||
if (_mqttServer != null) | |||||
{ | |||||
return; | |||||
} | |||||
JsonServerStorage storage = null; | |||||
if (ServerPersistRetainedMessages.IsChecked == true) | |||||
{ | |||||
storage = new JsonServerStorage(); | |||||
if (ServerClearRetainedMessages.IsChecked == true) | |||||
{ | |||||
storage.Clear(); | |||||
} | |||||
} | |||||
_mqttServer = new MqttFactory().CreateMqttServer(); | |||||
var options = new MqttServerOptions(); | |||||
options.DefaultEndpointOptions.Port = int.Parse(ServerPort.Text); | |||||
options.Storage = storage; | |||||
await _mqttServer.StartAsync(options); | |||||
} | |||||
private async void StopServer(object sender, RoutedEventArgs e) | |||||
{ | |||||
if (_mqttServer == null) | |||||
{ | |||||
return; | |||||
} | |||||
await _mqttServer.StopAsync(); | |||||
_mqttServer = null; | |||||
} | |||||
private void ClearReceivedMessages(object sender, RoutedEventArgs e) | |||||
{ | |||||
ReceivedMessages.Items.Clear(); | |||||
} | |||||
private async void ExecuteRpc(object sender, RoutedEventArgs e) | |||||
{ | |||||
var qos = MqttQualityOfServiceLevel.AtMostOnce; | |||||
if (RpcQoS1.IsChecked == true) | |||||
{ | |||||
qos = MqttQualityOfServiceLevel.AtLeastOnce; | |||||
} | |||||
if (RpcQoS2.IsChecked == true) | |||||
{ | |||||
qos = MqttQualityOfServiceLevel.ExactlyOnce; | |||||
} | |||||
var payload = new byte[0]; | |||||
if (RpcText.IsChecked == true) | |||||
{ | |||||
payload = Encoding.UTF8.GetBytes(RpcPayload.Text); | |||||
} | |||||
if (RpcBase64.IsChecked == true) | |||||
{ | |||||
payload = Convert.FromBase64String(RpcPayload.Text); | |||||
} | |||||
try | |||||
{ | |||||
var rpcClient = new MqttRpcClient(_mqttClient); | |||||
await rpcClient.EnableAsync(); | |||||
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), RpcMethod.Text, payload, qos); | |||||
await rpcClient.DisableAsync(); | |||||
RpcResponses.Items.Add(RpcMethod.Text + " >>> " + Encoding.UTF8.GetString(response)); | |||||
} | |||||
catch (MqttCommunicationTimedOutException) | |||||
{ | |||||
RpcResponses.Items.Add(RpcMethod.Text + " >>> [TIMEOUT]"); | |||||
} | |||||
} | |||||
private void ClearRpcResponses(object sender, RoutedEventArgs e) | |||||
{ | |||||
RpcResponses.Items.Clear(); | |||||
} | |||||
private async Task WikiCode() | private async Task WikiCode() | ||||
{ | { | ||||
{ | { | ||||
@@ -293,9 +384,9 @@ namespace MQTTnet.TestApp.UniversalWindows | |||||
{ | { | ||||
// Use secure TCP connection. | // Use secure TCP connection. | ||||
var options = new MqttClientOptionsBuilder() | var options = new MqttClientOptionsBuilder() | ||||
.WithTcpServer("broker.hivemq.com") | |||||
.WithTls() | |||||
.Build(); | |||||
.WithTcpServer("broker.hivemq.com") | |||||
.WithTls() | |||||
.Build(); | |||||
} | } | ||||
{ | { | ||||
@@ -480,48 +571,5 @@ namespace MQTTnet.TestApp.UniversalWindows | |||||
} | } | ||||
} | } | ||||
private async void StartServer(object sender, RoutedEventArgs e) | |||||
{ | |||||
if (_mqttServer != null) | |||||
{ | |||||
return; | |||||
} | |||||
JsonServerStorage storage = null; | |||||
if (ServerPersistRetainedMessages.IsChecked == true) | |||||
{ | |||||
storage = new JsonServerStorage(); | |||||
if (ServerClearRetainedMessages.IsChecked == true) | |||||
{ | |||||
storage.Clear(); | |||||
} | |||||
} | |||||
_mqttServer = new MqttFactory().CreateMqttServer(); | |||||
var options = new MqttServerOptions(); | |||||
options.DefaultEndpointOptions.Port = int.Parse(ServerPort.Text); | |||||
options.Storage = storage; | |||||
await _mqttServer.StartAsync(options); | |||||
} | |||||
private async void StopServer(object sender, RoutedEventArgs e) | |||||
{ | |||||
if (_mqttServer == null) | |||||
{ | |||||
return; | |||||
} | |||||
await _mqttServer.StopAsync(); | |||||
_mqttServer = null; | |||||
} | |||||
private void ClearReceivedMessages(object sender, RoutedEventArgs e) | |||||
{ | |||||
ReceivedMessages.Items.Clear(); | |||||
} | |||||
} | } | ||||
} | } |