@@ -18,7 +18,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov | |||
* TLS 1.2 support for client and server (but not UWP servers) | |||
* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS) | |||
* Lightweight (only the low level implementation of MQTT, no overhead) | |||
* Performance optimized (processing ~60.000 messages / second)* | |||
* Performance optimized (processing ~70.000 messages / second)* | |||
* Interfaces included for mocking and testing | |||
* Access to internal trace messages | |||
* Unit tested (~90 tests) | |||
@@ -50,14 +50,15 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov | |||
* .NET Standard 1.3+ | |||
* .NET Core 1.1+ | |||
* .NET Core App 1.1+ | |||
* Universal Windows Platform (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU, Windows 10 IoT Core) | |||
* .NET Framework 4.5.2+ (x86, x64, AnyCPU) | |||
* Mono 5.2+ | |||
* Universal Windows Platform (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU, Windows 10 IoT Core) | |||
* Xamarin.Android 7.5+ | |||
* Xamarin.iOS 10.14+ | |||
## Supported MQTT versions | |||
* 5.0.0 (planned) | |||
* 3.1.1 | |||
* 3.1.0 | |||
@@ -79,8 +80,7 @@ This library is used in the following projects: | |||
* MQTT Client Rx (Wrapper for Reactive Extensions, <https://github.com/1iveowl/MQTTClient.rx>) | |||
* MQTT Tester (MQTT client test app for [Android](https://play.google.com/store/apps/details?id=com.liveowl.mqtttester) and [iOS](https://itunes.apple.com/us/app/mqtt-tester/id1278621826?mt=8)) | |||
* Wirehome (Open Source Home Automation system for .NET, <https://github.com/chkr1011/Wirehome>) | |||
* HA4IoT (Open Source Home Automation system for .NET, <https://github.com/chkr1011/HA4IoT>) | |||
If you use this library and want to see your project here please let me know. | |||
@@ -82,11 +82,11 @@ namespace MQTTnet.Extensions.Rpc | |||
timeoutCts.Cancel(false); | |||
return result; | |||
} | |||
catch (TaskCanceledException taskCanceledException) | |||
catch (OperationCanceledException exception) | |||
{ | |||
if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) | |||
{ | |||
throw new MqttCommunicationTimedOutException(taskCanceledException); | |||
throw new MqttCommunicationTimedOutException(exception); | |||
} | |||
else | |||
{ | |||
@@ -271,21 +271,16 @@ namespace MQTTnet.Client | |||
private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken) | |||
{ | |||
if (cancellationToken.IsCancellationRequested) | |||
{ | |||
throw new TaskCanceledException(); | |||
} | |||
cancellationToken.ThrowIfCancellationRequested(); | |||
_sendTracker.Restart(); | |||
return _adapter.SendPacketAsync(packet, cancellationToken); | |||
} | |||
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket | |||
{ | |||
if (cancellationToken.IsCancellationRequested) | |||
{ | |||
throw new TaskCanceledException(); | |||
} | |||
cancellationToken.ThrowIfCancellationRequested(); | |||
_sendTracker.Restart(); | |||
@@ -524,7 +519,7 @@ namespace MQTTnet.Client | |||
{ | |||
await task.ConfigureAwait(false); | |||
} | |||
catch (TaskCanceledException) | |||
catch (OperationCanceledException) | |||
{ | |||
} | |||
} | |||
@@ -11,8 +11,10 @@ namespace MQTTnet.Internal | |||
private readonly LinkedList<TaskCompletionSource<bool>> _waiters = new LinkedList<TaskCompletionSource<bool>>(); | |||
private bool _isSignaled; | |||
public AsyncAutoResetEvent() : this(false) | |||
{ } | |||
public AsyncAutoResetEvent() | |||
: this(false) | |||
{ | |||
} | |||
public AsyncAutoResetEvent(bool signaled) | |||
{ | |||
@@ -58,27 +60,24 @@ namespace MQTTnet.Internal | |||
} | |||
var winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).ConfigureAwait(false); | |||
if (winner == tcs.Task) | |||
var taskWasSignaled = winner == tcs.Task; | |||
if (taskWasSignaled) | |||
{ | |||
// The task was signaled. | |||
return true; | |||
} | |||
else | |||
// We timed-out; remove our reference to the task. | |||
// This is an O(n) operation since waiters is a LinkedList<T>. | |||
lock (_waiters) | |||
{ | |||
// We timed-out; remove our reference to the task. | |||
// This is an O(n) operation since waiters is a LinkedList<T>. | |||
lock (_waiters) | |||
_waiters.Remove(tcs); | |||
if (winner.Status == TaskStatus.Canceled) | |||
{ | |||
_waiters.Remove(tcs); | |||
if (winner.Status == TaskStatus.Canceled) | |||
{ | |||
throw new OperationCanceledException(cancellationToken); | |||
} | |||
else | |||
{ | |||
throw new TimeoutException(); | |||
} | |||
throw new OperationCanceledException(cancellationToken); | |||
} | |||
throw new TimeoutException(); | |||
} | |||
} | |||
@@ -17,7 +17,7 @@ namespace MQTTnet.Internal | |||
public Task<IDisposable> LockAsync(CancellationToken cancellationToken) | |||
{ | |||
Task wait = _semaphore.WaitAsync(cancellationToken); | |||
var wait = _semaphore.WaitAsync(cancellationToken); | |||
return wait.IsCompleted ? | |||
_releaser : | |||
wait.ContinueWith((_, state) => (IDisposable)state, | |||
@@ -29,11 +29,7 @@ namespace MQTTnet.Serializer | |||
var bytesRead = await channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false); | |||
if (bytesRead <= 0) | |||
{ | |||
if (cancellationToken.IsCancellationRequested) | |||
{ | |||
throw new TaskCanceledException(); | |||
} | |||
cancellationToken.ThrowIfCancellationRequested(); | |||
ExceptionHelper.ThrowGracefulSocketClose(); | |||
} | |||
@@ -59,6 +55,8 @@ namespace MQTTnet.Serializer | |||
while ((encodedByte & 128) != 0) | |||
{ | |||
cancellationToken.ThrowIfCancellationRequested(); | |||
// Here the async/await pattern is not used becuase the overhead of context switches | |||
// is too big for reading 1 byte in a row. We expect that the remaining data was sent | |||
// directly after the initial bytes. If the client disconnects just in this moment we | |||
@@ -83,6 +81,7 @@ namespace MQTTnet.Serializer | |||
var readCount = channel.ReadAsync(buffer, 0, 1, cancellationToken).GetAwaiter().GetResult(); | |||
if (readCount <= 0) | |||
{ | |||
cancellationToken.ThrowIfCancellationRequested(); | |||
ExceptionHelper.ThrowGracefulSocketClose(); | |||
} | |||
@@ -0,0 +1,23 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Packets; | |||
namespace MQTTnet.Server | |||
{ | |||
public interface IMqttClientSession : IDisposable | |||
{ | |||
string ClientId { get; } | |||
void FillStatus(MqttClientSessionStatus status); | |||
void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket); | |||
void ClearPendingApplicationMessages(); | |||
Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter); | |||
void Stop(MqttClientDisconnectType disconnectType); | |||
Task SubscribeAsync(IList<TopicFilter> topicFilters); | |||
Task UnsubscribeAsync(IList<string> topicFilters); | |||
} | |||
} |
@@ -12,19 +12,17 @@ namespace MQTTnet.Server | |||
private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch(); | |||
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch(); | |||
private readonly IMqttClientSession _clientSession; | |||
private readonly IMqttNetChildLogger _logger; | |||
private readonly string _clientId; | |||
private readonly Action _callback; | |||
private bool _isPaused; | |||
private Task _workerTask; | |||
public MqttClientKeepAliveMonitor(string clientId, Action callback, IMqttNetChildLogger logger) | |||
public MqttClientKeepAliveMonitor(IMqttClientSession clientSession, IMqttNetChildLogger logger) | |||
{ | |||
if (logger == null) throw new ArgumentNullException(nameof(logger)); | |||
_clientId = clientId; | |||
_callback = callback; | |||
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession)); | |||
_logger = logger.CreateChildLogger(nameof(MqttClientKeepAliveMonitor)); | |||
} | |||
@@ -39,7 +37,7 @@ namespace MQTTnet.Server | |||
return; | |||
} | |||
_workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken), cancellationToken); | |||
Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken), cancellationToken); | |||
} | |||
public void Pause() | |||
@@ -74,9 +72,9 @@ namespace MQTTnet.Server | |||
// Values described here: [MQTT-3.1.2-24]. | |||
if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D) | |||
{ | |||
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientId); | |||
_callback(); | |||
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientSession.ClientId); | |||
_clientSession.Stop(MqttClientDisconnectType.NotClean); | |||
return; | |||
} | |||
@@ -88,11 +86,11 @@ namespace MQTTnet.Server | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.Error(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId); | |||
_logger.Error(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientSession.ClientId); | |||
} | |||
finally | |||
{ | |||
_logger.Verbose("Client {0}: Stopped checking keep alive timeout.", _clientId); | |||
_logger.Verbose("Client {0}: Stopped checking keep alive timeout.", _clientSession.ClientId); | |||
} | |||
} | |||
} | |||
@@ -1,5 +1,5 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Adapter; | |||
@@ -13,13 +13,13 @@ namespace MQTTnet.Server | |||
{ | |||
public class MqttClientPendingPacketsQueue : IDisposable | |||
{ | |||
private readonly Queue<MqttBasePacket> _queue = new Queue<MqttBasePacket>(); | |||
private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent(); | |||
private readonly IMqttServerOptions _options; | |||
private readonly MqttClientSession _clientSession; | |||
private readonly IMqttNetChildLogger _logger; | |||
private ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>(); | |||
public MqttClientPendingPacketsQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetChildLogger logger) | |||
{ | |||
if (logger == null) throw new ArgumentNullException(nameof(logger)); | |||
@@ -29,7 +29,16 @@ namespace MQTTnet.Server | |||
_logger = logger.CreateChildLogger(nameof(MqttClientPendingPacketsQueue)); | |||
} | |||
public int Count => _queue.Count; | |||
public int Count | |||
{ | |||
get | |||
{ | |||
lock (_queue) | |||
{ | |||
return _queue.Count; | |||
} | |||
} | |||
} | |||
public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken) | |||
{ | |||
@@ -42,25 +51,29 @@ namespace MQTTnet.Server | |||
Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken); | |||
} | |||
public void Enqueue(MqttBasePacket packet) | |||
{ | |||
if (packet == null) throw new ArgumentNullException(nameof(packet)); | |||
if (_queue.Count >= _options.MaxPendingMessagesPerClient) | |||
lock (_queue) | |||
{ | |||
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage) | |||
if (_queue.Count >= _options.MaxPendingMessagesPerClient) | |||
{ | |||
return; | |||
} | |||
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage) | |||
{ | |||
return; | |||
} | |||
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage) | |||
{ | |||
_queue.TryDequeue(out _); | |||
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage) | |||
{ | |||
_queue.Dequeue(); | |||
} | |||
} | |||
_queue.Enqueue(packet); | |||
} | |||
_queue.Enqueue(packet); | |||
_queueAutoResetEvent.Set(); | |||
_logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId); | |||
@@ -68,13 +81,14 @@ namespace MQTTnet.Server | |||
public void Clear() | |||
{ | |||
var newQueue = new ConcurrentQueue<MqttBasePacket>(); | |||
Interlocked.Exchange(ref _queue, newQueue); | |||
lock (_queue) | |||
{ | |||
_queue.Clear(); | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
} | |||
private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) | |||
@@ -100,13 +114,17 @@ namespace MQTTnet.Server | |||
MqttBasePacket packet = null; | |||
try | |||
{ | |||
if (_queue.IsEmpty) | |||
lock (_queue) | |||
{ | |||
await _queueAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false); | |||
if (_queue.Count > 0) | |||
{ | |||
packet = _queue.Dequeue(); | |||
} | |||
} | |||
if (!_queue.TryDequeue(out packet)) | |||
if (packet == null) | |||
{ | |||
await _queueAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false); | |||
return; | |||
} | |||
@@ -115,7 +133,7 @@ namespace MQTTnet.Server | |||
return; | |||
} | |||
await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); | |||
adapter.SendPacketAsync(packet, cancellationToken).GetAwaiter().GetResult(); | |||
_logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); | |||
} | |||
@@ -12,7 +12,7 @@ using MQTTnet.Protocol; | |||
namespace MQTTnet.Server | |||
{ | |||
public class MqttClientSession : IDisposable | |||
public class MqttClientSession : IMqttClientSession | |||
{ | |||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); | |||
@@ -47,7 +47,7 @@ namespace MQTTnet.Server | |||
_logger = logger.CreateChildLogger(nameof(MqttClientSession)); | |||
_keepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, () => Stop(MqttClientDisconnectType.NotClean), _logger); | |||
_keepAliveMonitor = new MqttClientKeepAliveMonitor(this, _logger); | |||
_subscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server); | |||
_pendingPacketsQueue = new MqttClientPendingPacketsQueue(_options, this, _logger); | |||
} | |||
@@ -89,7 +89,7 @@ namespace MQTTnet.Server | |||
if (packet != null) | |||
{ | |||
_keepAliveMonitor.PacketReceived(packet); | |||
await ProcessReceivedPacketAsync(adapter, packet, _cancellationTokenSource.Token).ConfigureAwait(false); | |||
ProcessReceivedPacket(adapter, packet, _cancellationTokenSource.Token); | |||
} | |||
} | |||
} | |||
@@ -102,7 +102,7 @@ namespace MQTTnet.Server | |||
{ | |||
if (exception is MqttCommunicationClosedGracefullyException) | |||
{ | |||
_logger.Verbose("Client '{0}': Connection closed gracefully.", ClientId); ; | |||
_logger.Verbose("Client '{0}': Connection closed gracefully.", ClientId); | |||
} | |||
else | |||
{ | |||
@@ -113,7 +113,7 @@ namespace MQTTnet.Server | |||
{ | |||
_logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId); | |||
} | |||
Stop(MqttClientDisconnectType.NotClean); | |||
} | |||
finally | |||
@@ -123,7 +123,7 @@ namespace MQTTnet.Server | |||
_adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; | |||
_adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted; | |||
} | |||
_adapter = null; | |||
_cancellationTokenSource?.Dispose(); | |||
@@ -149,7 +149,7 @@ namespace MQTTnet.Server | |||
if (_willMessage != null && !_wasCleanDisconnect) | |||
{ | |||
_sessionsManager.EnqueueApplicationMessage(this, _willMessage); | |||
_sessionsManager.EnqueueApplicationMessage(this, _willMessage.ToPublishPacket()); | |||
} | |||
_willMessage = null; | |||
@@ -160,18 +160,24 @@ namespace MQTTnet.Server | |||
} | |||
} | |||
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) | |||
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket) | |||
{ | |||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | |||
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); | |||
var checkSubscriptionsResult = _subscriptionsManager.CheckSubscriptions(applicationMessage); | |||
var checkSubscriptionsResult = _subscriptionsManager.CheckSubscriptions(publishPacket.Topic, publishPacket.QualityOfServiceLevel); | |||
if (!checkSubscriptionsResult.IsSubscribed) | |||
{ | |||
return; | |||
} | |||
var publishPacket = applicationMessage.ToPublishPacket(); | |||
publishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel; | |||
publishPacket = new MqttPublishPacket | |||
{ | |||
Topic = publishPacket.Topic, | |||
Payload = publishPacket.Payload, | |||
QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel, | |||
Retain = false, | |||
Dup = false | |||
}; | |||
if (publishPacket.QualityOfServiceLevel > 0) | |||
{ | |||
@@ -184,15 +190,19 @@ namespace MQTTnet.Server | |||
senderClientSession?.ClientId, | |||
ClientId, | |||
publishPacket.ToApplicationMessage()); | |||
_options.ClientMessageQueueInterceptor?.Invoke(context); | |||
if (!context.AcceptEnqueue || context.ApplicationMessage == null) | |||
{ | |||
return; | |||
} | |||
publishPacket.Topic = context.ApplicationMessage.Topic; | |||
publishPacket.Payload = context.ApplicationMessage.Payload; | |||
publishPacket.QualityOfServiceLevel = context.ApplicationMessage.QualityOfServiceLevel; | |||
} | |||
_pendingPacketsQueue.Enqueue(publishPacket); | |||
} | |||
@@ -233,21 +243,29 @@ namespace MQTTnet.Server | |||
_cancellationTokenSource?.Dispose(); | |||
} | |||
private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken) | |||
private void ProcessReceivedPacket(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken) | |||
{ | |||
if (packet is MqttPublishPacket publishPacket) | |||
{ | |||
return HandleIncomingPublishPacketAsync(adapter, publishPacket, cancellationToken); | |||
HandleIncomingPublishPacket(adapter, publishPacket, cancellationToken); | |||
return; | |||
} | |||
if (packet is MqttPingReqPacket) | |||
{ | |||
return adapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken); | |||
adapter.SendPacketAsync(new MqttPingRespPacket(), cancellationToken).GetAwaiter().GetResult(); | |||
return; | |||
} | |||
if (packet is MqttPubRelPacket pubRelPacket) | |||
{ | |||
return HandleIncomingPubRelPacketAsync(adapter, pubRelPacket, cancellationToken); | |||
var responsePacket = new MqttPubCompPacket | |||
{ | |||
PacketIdentifier = pubRelPacket.PacketIdentifier | |||
}; | |||
adapter.SendPacketAsync(responsePacket, cancellationToken).GetAwaiter().GetResult(); | |||
return; | |||
} | |||
if (packet is MqttPubRecPacket pubRecPacket) | |||
@@ -257,40 +275,41 @@ namespace MQTTnet.Server | |||
PacketIdentifier = pubRecPacket.PacketIdentifier | |||
}; | |||
return adapter.SendPacketAsync(responsePacket, cancellationToken); | |||
adapter.SendPacketAsync(responsePacket, cancellationToken).GetAwaiter().GetResult(); | |||
return; | |||
} | |||
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) | |||
{ | |||
// Discard message. | |||
return Task.FromResult(0); | |||
return; | |||
} | |||
if (packet is MqttSubscribePacket subscribePacket) | |||
{ | |||
return HandleIncomingSubscribePacketAsync(adapter, subscribePacket, cancellationToken); | |||
HandleIncomingSubscribePacket(adapter, subscribePacket, cancellationToken); | |||
return; | |||
} | |||
if (packet is MqttUnsubscribePacket unsubscribePacket) | |||
{ | |||
return HandleIncomingUnsubscribePacketAsync(adapter, unsubscribePacket, cancellationToken); | |||
HandleIncomingUnsubscribePacket(adapter, unsubscribePacket, cancellationToken); | |||
return; | |||
} | |||
if (packet is MqttDisconnectPacket) | |||
{ | |||
Stop(MqttClientDisconnectType.Clean); | |||
return Task.FromResult(0); | |||
return; | |||
} | |||
if (packet is MqttConnectPacket) | |||
{ | |||
Stop(MqttClientDisconnectType.NotClean); | |||
return Task.FromResult(0); | |||
return; | |||
} | |||
_logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); | |||
Stop(MqttClientDisconnectType.NotClean); | |||
return Task.FromResult(0); | |||
} | |||
private void EnqueueSubscribedRetainedMessages(ICollection<TopicFilter> topicFilters) | |||
@@ -298,14 +317,14 @@ namespace MQTTnet.Server | |||
var retainedMessages = _retainedMessagesManager.GetSubscribedMessages(topicFilters); | |||
foreach (var applicationMessage in retainedMessages) | |||
{ | |||
EnqueueApplicationMessage(null, applicationMessage); | |||
EnqueueApplicationMessage(null, applicationMessage.ToPublishPacket()); | |||
} | |||
} | |||
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) | |||
private void HandleIncomingSubscribePacket(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) | |||
{ | |||
var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket); | |||
await adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).ConfigureAwait(false); | |||
adapter.SendPacketAsync(subscribeResult.ResponsePacket, cancellationToken).GetAwaiter().GetResult(); | |||
if (subscribeResult.CloseConnection) | |||
{ | |||
@@ -316,30 +335,30 @@ namespace MQTTnet.Server | |||
EnqueueSubscribedRetainedMessages(subscribePacket.TopicFilters); | |||
} | |||
private Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) | |||
private void HandleIncomingUnsubscribePacket(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) | |||
{ | |||
var unsubscribeResult = _subscriptionsManager.Unsubscribe(unsubscribePacket); | |||
return adapter.SendPacketAsync(unsubscribeResult, cancellationToken); | |||
adapter.SendPacketAsync(unsubscribeResult, cancellationToken).GetAwaiter().GetResult(); | |||
} | |||
private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) | |||
private void HandleIncomingPublishPacket(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) | |||
{ | |||
var applicationMessage = publishPacket.ToApplicationMessage(); | |||
switch (applicationMessage.QualityOfServiceLevel) | |||
switch (publishPacket.QualityOfServiceLevel) | |||
{ | |||
case MqttQualityOfServiceLevel.AtMostOnce: | |||
{ | |||
_sessionsManager.EnqueueApplicationMessage(this, applicationMessage); | |||
return Task.FromResult(0); | |||
HandleIncomingPublishPacketWithQoS0(publishPacket); | |||
break; | |||
} | |||
case MqttQualityOfServiceLevel.AtLeastOnce: | |||
{ | |||
return HandleIncomingPublishPacketWithQoS1(adapter, applicationMessage, publishPacket, cancellationToken); | |||
HandleIncomingPublishPacketWithQoS1(adapter, publishPacket, cancellationToken); | |||
break; | |||
} | |||
case MqttQualityOfServiceLevel.ExactlyOnce: | |||
{ | |||
return HandleIncomingPublishPacketWithQoS2(adapter, applicationMessage, publishPacket, cancellationToken); | |||
HandleIncomingPublishPacketWithQoS2(adapter, publishPacket, cancellationToken); | |||
break; | |||
} | |||
default: | |||
{ | |||
@@ -348,27 +367,40 @@ namespace MQTTnet.Server | |||
} | |||
} | |||
private Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) | |||
private void HandleIncomingPublishPacketWithQoS0(MqttPublishPacket publishPacket) | |||
{ | |||
_sessionsManager.EnqueueApplicationMessage(this, applicationMessage); | |||
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }; | |||
return adapter.SendPacketAsync(response, cancellationToken); | |||
_sessionsManager.EnqueueApplicationMessage(this, publishPacket); | |||
} | |||
private Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) | |||
private void HandleIncomingPublishPacketWithQoS1( | |||
IMqttChannelAdapter adapter, | |||
MqttPublishPacket publishPacket, | |||
CancellationToken cancellationToken) | |||
{ | |||
// QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) | |||
_sessionsManager.EnqueueApplicationMessage(this, applicationMessage); | |||
_sessionsManager.EnqueueApplicationMessage(this, publishPacket); | |||
var response = new MqttPubAckPacket | |||
{ | |||
PacketIdentifier = publishPacket.PacketIdentifier | |||
}; | |||
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; | |||
return adapter.SendPacketAsync(response, cancellationToken); | |||
adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult(); | |||
} | |||
private static Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) | |||
private void HandleIncomingPublishPacketWithQoS2( | |||
IMqttChannelAdapter adapter, | |||
MqttPublishPacket publishPacket, | |||
CancellationToken cancellationToken) | |||
{ | |||
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }; | |||
return adapter.SendPacketAsync(response, cancellationToken); | |||
// QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery) | |||
_sessionsManager.EnqueueApplicationMessage(this, publishPacket); | |||
var response = new MqttPubRecPacket | |||
{ | |||
PacketIdentifier = publishPacket.PacketIdentifier | |||
}; | |||
adapter.SendPacketAsync(response, cancellationToken).GetAwaiter().GetResult(); | |||
} | |||
private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) | |||
@@ -6,6 +6,7 @@ using System.Threading.Tasks; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Exceptions; | |||
using MQTTnet.Internal; | |||
using MQTTnet.Packets; | |||
using MQTTnet.Protocol; | |||
@@ -41,7 +42,7 @@ namespace MQTTnet.Server | |||
Task.Factory.StartNew(() => ProcessQueuedApplicationMessages(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); | |||
} | |||
public Task StopAsync() | |||
public void Stop() | |||
{ | |||
foreach (var session in _sessions) | |||
{ | |||
@@ -49,7 +50,6 @@ namespace MQTTnet.Server | |||
} | |||
_sessions.Clear(); | |||
return Task.FromResult(0); | |||
} | |||
public Task StartSession(IMqttChannelAdapter clientAdapter) | |||
@@ -71,11 +71,11 @@ namespace MQTTnet.Server | |||
return Task.FromResult((IList<IMqttClientSessionStatus>)result); | |||
} | |||
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) | |||
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket) | |||
{ | |||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | |||
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); | |||
_messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, applicationMessage), _cancellationToken); | |||
_messageQueue.Add(new MqttEnqueuedApplicationMessage(senderClientSession, publishPacket), _cancellationToken); | |||
} | |||
public Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters) | |||
@@ -118,7 +118,7 @@ namespace MQTTnet.Server | |||
{ | |||
var enqueuedApplicationMessage = _messageQueue.Take(cancellationToken); | |||
var sender = enqueuedApplicationMessage.Sender; | |||
var applicationMessage = enqueuedApplicationMessage.ApplicationMessage; | |||
var applicationMessage = enqueuedApplicationMessage.PublishPacket.ToApplicationMessage(); | |||
var interceptorContext = InterceptApplicationMessage(sender, applicationMessage); | |||
if (interceptorContext != null) | |||
@@ -145,7 +145,7 @@ namespace MQTTnet.Server | |||
foreach (var clientSession in _sessions.Values) | |||
{ | |||
clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage); | |||
clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage.ToPublishPacket()); | |||
} | |||
} | |||
catch (OperationCanceledException) | |||
@@ -1,5 +1,4 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using MQTTnet.Packets; | |||
@@ -9,7 +8,7 @@ namespace MQTTnet.Server | |||
{ | |||
public class MqttClientSubscriptionsManager | |||
{ | |||
private readonly ConcurrentDictionary<string, MqttQualityOfServiceLevel> _subscriptions = new ConcurrentDictionary<string, MqttQualityOfServiceLevel>(); | |||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>(); | |||
private readonly IMqttServerOptions _options; | |||
private readonly MqttServer _server; | |||
private readonly string _clientId; | |||
@@ -54,7 +53,11 @@ namespace MQTTnet.Server | |||
if (interceptorContext.AcceptSubscription) | |||
{ | |||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; | |||
lock (_subscriptions) | |||
{ | |||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; | |||
} | |||
_server.OnClientSubscribedTopic(_clientId, topicFilter); | |||
} | |||
} | |||
@@ -66,10 +69,14 @@ namespace MQTTnet.Server | |||
{ | |||
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); | |||
foreach (var topicFilter in unsubscribePacket.TopicFilters) | |||
lock (_subscriptions) | |||
{ | |||
_subscriptions.TryRemove(topicFilter, out _); | |||
_server.OnClientUnsubscribedTopic(_clientId, topicFilter); | |||
foreach (var topicFilter in unsubscribePacket.TopicFilters) | |||
{ | |||
_subscriptions.Remove(topicFilter); | |||
_server.OnClientUnsubscribedTopic(_clientId, topicFilter); | |||
} | |||
} | |||
return new MqttUnsubAckPacket | |||
@@ -78,19 +85,21 @@ namespace MQTTnet.Server | |||
}; | |||
} | |||
public CheckSubscriptionsResult CheckSubscriptions(MqttApplicationMessage applicationMessage) | |||
public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel) | |||
{ | |||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | |||
var qosLevels = new HashSet<MqttQualityOfServiceLevel>(); | |||
foreach (var subscription in _subscriptions) | |||
lock (_subscriptions) | |||
{ | |||
if (!MqttTopicFilterComparer.IsMatch(applicationMessage.Topic, subscription.Key)) | |||
foreach (var subscription in _subscriptions) | |||
{ | |||
continue; | |||
} | |||
if (!MqttTopicFilterComparer.IsMatch(topic, subscription.Key)) | |||
{ | |||
continue; | |||
} | |||
qosLevels.Add(subscription.Value); | |||
qosLevels.Add(subscription.Value); | |||
} | |||
} | |||
if (qosLevels.Count == 0) | |||
@@ -101,7 +110,7 @@ namespace MQTTnet.Server | |||
}; | |||
} | |||
return CreateSubscriptionResult(applicationMessage, qosLevels); | |||
return CreateSubscriptionResult(qosLevel, qosLevels); | |||
} | |||
private static MqttSubscribeReturnCode ConvertToMaximumQoS(MqttQualityOfServiceLevel qualityOfServiceLevel) | |||
@@ -122,12 +131,12 @@ namespace MQTTnet.Server | |||
return interceptorContext; | |||
} | |||
private static CheckSubscriptionsResult CreateSubscriptionResult(MqttApplicationMessage applicationMessage, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels) | |||
private static CheckSubscriptionsResult CreateSubscriptionResult(MqttQualityOfServiceLevel qosLevel, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels) | |||
{ | |||
MqttQualityOfServiceLevel effectiveQoS; | |||
if (subscribedQoSLevels.Contains(applicationMessage.QualityOfServiceLevel)) | |||
if (subscribedQoSLevels.Contains(qosLevel)) | |||
{ | |||
effectiveQoS = applicationMessage.QualityOfServiceLevel; | |||
effectiveQoS = qosLevel; | |||
} | |||
else if (subscribedQoSLevels.Count == 1) | |||
{ | |||
@@ -1,15 +1,17 @@ | |||
namespace MQTTnet.Server | |||
using MQTTnet.Packets; | |||
namespace MQTTnet.Server | |||
{ | |||
public class MqttEnqueuedApplicationMessage | |||
{ | |||
public MqttEnqueuedApplicationMessage(MqttClientSession sender, MqttApplicationMessage applicationMessage) | |||
public MqttEnqueuedApplicationMessage(MqttClientSession sender, MqttPublishPacket publishPacket) | |||
{ | |||
Sender = sender; | |||
ApplicationMessage = applicationMessage; | |||
PublishPacket = publishPacket; | |||
} | |||
public MqttClientSession Sender { get; } | |||
public MqttApplicationMessage ApplicationMessage { get; } | |||
public MqttPublishPacket PublishPacket { get; } | |||
} | |||
} |
@@ -1,5 +1,4 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
@@ -9,7 +8,8 @@ namespace MQTTnet.Server | |||
{ | |||
public class MqttRetainedMessagesManager | |||
{ | |||
private readonly ConcurrentDictionary<string, MqttApplicationMessage> _messages = new ConcurrentDictionary<string, MqttApplicationMessage>(); | |||
private readonly Dictionary<string, MqttApplicationMessage> _messages = new Dictionary<string, MqttApplicationMessage>(); | |||
private readonly IMqttNetChildLogger _logger; | |||
private readonly IMqttServerOptions _options; | |||
@@ -31,10 +31,13 @@ namespace MQTTnet.Server | |||
{ | |||
var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync().ConfigureAwait(false); | |||
_messages.Clear(); | |||
foreach (var retainedMessage in retainedMessages) | |||
lock (_messages) | |||
{ | |||
_messages[retainedMessage.Topic] = retainedMessage; | |||
_messages.Clear(); | |||
foreach (var retainedMessage in retainedMessages) | |||
{ | |||
_messages[retainedMessage.Topic] = retainedMessage; | |||
} | |||
} | |||
} | |||
catch (Exception exception) | |||
@@ -61,17 +64,20 @@ namespace MQTTnet.Server | |||
{ | |||
var retainedMessages = new List<MqttApplicationMessage>(); | |||
foreach (var retainedMessage in _messages.Values) | |||
lock (_messages) | |||
{ | |||
foreach (var topicFilter in topicFilters) | |||
foreach (var retainedMessage in _messages.Values) | |||
{ | |||
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic)) | |||
foreach (var topicFilter in topicFilters) | |||
{ | |||
continue; | |||
} | |||
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic)) | |||
{ | |||
continue; | |||
} | |||
retainedMessages.Add(retainedMessage); | |||
break; | |||
retainedMessages.Add(retainedMessage); | |||
break; | |||
} | |||
} | |||
} | |||
@@ -82,28 +88,31 @@ namespace MQTTnet.Server | |||
{ | |||
var saveIsRequired = false; | |||
if (applicationMessage.Payload?.Length == 0) | |||
{ | |||
saveIsRequired = _messages.TryRemove(applicationMessage.Topic, out _); | |||
_logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); | |||
} | |||
else | |||
lock (_messages) | |||
{ | |||
if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage)) | |||
if (applicationMessage.Payload?.Length == 0) | |||
{ | |||
_messages[applicationMessage.Topic] = applicationMessage; | |||
saveIsRequired = true; | |||
saveIsRequired = _messages.Remove(applicationMessage.Topic); | |||
_logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); | |||
} | |||
else | |||
{ | |||
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0])) | |||
if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage)) | |||
{ | |||
_messages[applicationMessage.Topic] = applicationMessage; | |||
saveIsRequired = true; | |||
} | |||
} | |||
else | |||
{ | |||
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0])) | |||
{ | |||
_messages[applicationMessage.Topic] = applicationMessage; | |||
saveIsRequired = true; | |||
} | |||
} | |||
_logger.Info("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic); | |||
_logger.Info("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic); | |||
} | |||
} | |||
if (!saveIsRequired) | |||
@@ -113,7 +122,13 @@ namespace MQTTnet.Server | |||
if (saveIsRequired && _options.Storage != null) | |||
{ | |||
await _options.Storage.SaveRetainedMessagesAsync(_messages.Values.ToList()).ConfigureAwait(false); | |||
List<MqttApplicationMessage> messages; | |||
lock (_messages) | |||
{ | |||
messages = _messages.Values.ToList(); | |||
} | |||
await _options.Storage.SaveRetainedMessagesAsync(messages).ConfigureAwait(false); | |||
} | |||
} | |||
} | |||
@@ -5,6 +5,7 @@ using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Internal; | |||
namespace MQTTnet.Server | |||
{ | |||
@@ -65,7 +66,7 @@ namespace MQTTnet.Server | |||
if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started."); | |||
_clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage); | |||
_clientSessionsManager.EnqueueApplicationMessage(null, applicationMessage.ToPublishPacket()); | |||
return Task.FromResult(0); | |||
} | |||
@@ -104,22 +105,23 @@ namespace MQTTnet.Server | |||
} | |||
_cancellationTokenSource.Cancel(false); | |||
_cancellationTokenSource.Dispose(); | |||
foreach (var adapter in _adapters) | |||
{ | |||
adapter.ClientAccepted -= OnClientAccepted; | |||
await adapter.StopAsync().ConfigureAwait(false); | |||
} | |||
await _clientSessionsManager.StopAsync().ConfigureAwait(false); | |||
_clientSessionsManager.Stop(); | |||
_logger.Info("Stopped."); | |||
Stopped?.Invoke(this, EventArgs.Empty); | |||
} | |||
finally | |||
{ | |||
_cancellationTokenSource?.Dispose(); | |||
_cancellationTokenSource = null; | |||
_retainedMessagesManager = null; | |||
_clientSessionsManager = null; | |||
} | |||
@@ -1,5 +1,9 @@ | |||
using System.Threading; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Microsoft.VisualStudio.TestTools.UnitTesting; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Packets; | |||
using MQTTnet.Server; | |||
@@ -12,39 +16,31 @@ namespace MQTTnet.Core.Tests | |||
[TestMethod] | |||
public void KeepAlive_Timeout() | |||
{ | |||
var timeoutCalledCount = 0; | |||
var clientSession = new TestClientSession(); | |||
var monitor = new MqttClientKeepAliveMonitor(clientSession, new MqttNetLogger().CreateChildLogger()); | |||
var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate | |||
{ | |||
timeoutCalledCount++; | |||
}, new MqttNetLogger().CreateChildLogger("")); | |||
Assert.AreEqual(0, timeoutCalledCount); | |||
Assert.AreEqual(0, clientSession.StopCalledCount); | |||
monitor.Start(1, CancellationToken.None); | |||
Assert.AreEqual(0, timeoutCalledCount); | |||
Assert.AreEqual(0, clientSession.StopCalledCount); | |||
Thread.Sleep(2000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification. | |||
Assert.AreEqual(1, timeoutCalledCount); | |||
Assert.AreEqual(1, clientSession.StopCalledCount); | |||
} | |||
[TestMethod] | |||
public void KeepAlive_NoTimeout() | |||
{ | |||
var timeoutCalledCount = 0; | |||
var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate | |||
{ | |||
timeoutCalledCount++; | |||
}, new MqttNetLogger().CreateChildLogger("")); | |||
var clientSession = new TestClientSession(); | |||
var monitor = new MqttClientKeepAliveMonitor(clientSession, new MqttNetLogger().CreateChildLogger()); | |||
Assert.AreEqual(0, timeoutCalledCount); | |||
Assert.AreEqual(0, clientSession.StopCalledCount); | |||
monitor.Start(1, CancellationToken.None); | |||
Assert.AreEqual(0, timeoutCalledCount); | |||
Assert.AreEqual(0, clientSession.StopCalledCount); | |||
// Simulate traffic. | |||
Thread.Sleep(1000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification. | |||
@@ -53,11 +49,57 @@ namespace MQTTnet.Core.Tests | |||
monitor.PacketReceived(new MqttPublishPacket()); | |||
Thread.Sleep(1000); | |||
Assert.AreEqual(0, timeoutCalledCount); | |||
Assert.AreEqual(0, clientSession.StopCalledCount); | |||
Thread.Sleep(2000); | |||
Assert.AreEqual(1, timeoutCalledCount); | |||
Assert.AreEqual(1, clientSession.StopCalledCount); | |||
} | |||
private class TestClientSession : IMqttClientSession | |||
{ | |||
public string ClientId { get; } | |||
public int StopCalledCount { get; set; } | |||
public void FillStatus(MqttClientSessionStatus status) | |||
{ | |||
throw new NotSupportedException(); | |||
} | |||
public void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket) | |||
{ | |||
throw new NotSupportedException(); | |||
} | |||
public void ClearPendingApplicationMessages() | |||
{ | |||
throw new NotSupportedException(); | |||
} | |||
public Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) | |||
{ | |||
throw new NotSupportedException(); | |||
} | |||
public void Stop(MqttClientDisconnectType disconnectType) | |||
{ | |||
StopCalledCount++; | |||
} | |||
public Task SubscribeAsync(IList<TopicFilter> topicFilters) | |||
{ | |||
throw new NotSupportedException(); | |||
} | |||
public Task UnsubscribeAsync(IList<string> topicFilters) | |||
{ | |||
throw new NotSupportedException(); | |||
} | |||
public void Dispose() | |||
{ | |||
} | |||
} | |||
} | |||
} |
@@ -20,13 +20,7 @@ namespace MQTTnet.Core.Tests | |||
sm.Subscribe(sp); | |||
var pp = new MqttApplicationMessage | |||
{ | |||
Topic = "A/B/C", | |||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | |||
}; | |||
var result = sm.CheckSubscriptions(pp); | |||
var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce); | |||
Assert.IsTrue(result.IsSubscribed); | |||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); | |||
} | |||
@@ -41,13 +35,7 @@ namespace MQTTnet.Core.Tests | |||
sm.Subscribe(sp); | |||
var pp = new MqttApplicationMessage | |||
{ | |||
Topic = "A/B/C", | |||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce | |||
}; | |||
var result = sm.CheckSubscriptions(pp); | |||
var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce); | |||
Assert.IsTrue(result.IsSubscribed); | |||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); | |||
} | |||
@@ -63,13 +51,7 @@ namespace MQTTnet.Core.Tests | |||
sm.Subscribe(sp); | |||
var pp = new MqttApplicationMessage | |||
{ | |||
Topic = "A/B/C", | |||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce | |||
}; | |||
var result = sm.CheckSubscriptions(pp); | |||
var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce); | |||
Assert.IsTrue(result.IsSubscribed); | |||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce); | |||
} | |||
@@ -84,13 +66,7 @@ namespace MQTTnet.Core.Tests | |||
sm.Subscribe(sp); | |||
var pp = new MqttApplicationMessage | |||
{ | |||
Topic = "A/B/X", | |||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | |||
}; | |||
Assert.IsFalse(sm.CheckSubscriptions(pp).IsSubscribed); | |||
Assert.IsFalse(sm.CheckSubscriptions("A/B/X", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed); | |||
} | |||
[TestMethod] | |||
@@ -103,19 +79,13 @@ namespace MQTTnet.Core.Tests | |||
sm.Subscribe(sp); | |||
var pp = new MqttApplicationMessage | |||
{ | |||
Topic = "A/B/C", | |||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce | |||
}; | |||
Assert.IsTrue(sm.CheckSubscriptions(pp).IsSubscribed); | |||
Assert.IsTrue(sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed); | |||
var up = new MqttUnsubscribePacket(); | |||
up.TopicFilters.Add("A/B/C"); | |||
sm.Unsubscribe(up); | |||
Assert.IsFalse(sm.CheckSubscriptions(pp).IsSubscribed); | |||
Assert.IsFalse(sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed); | |||
} | |||
} | |||
} |
@@ -12,7 +12,48 @@ namespace MQTTnet.TestApp.NetCore | |||
{ | |||
public static class PerformanceTest | |||
{ | |||
public static void Run() | |||
public static void RunClientOnly() | |||
{ | |||
try | |||
{ | |||
var options = new MqttClientOptions | |||
{ | |||
ChannelOptions = new MqttClientTcpOptions | |||
{ | |||
Server = "127.0.0.1" | |||
}, | |||
CleanSession = true | |||
}; | |||
var client = new MqttFactory().CreateMqttClient(); | |||
client.ConnectAsync(options).GetAwaiter().GetResult(); | |||
var message = CreateMessage(); | |||
var stopwatch = new Stopwatch(); | |||
for (var i = 0; i < 10; i++) | |||
{ | |||
var sentMessagesCount = 0; | |||
stopwatch.Restart(); | |||
while (stopwatch.ElapsedMilliseconds < 1000) | |||
{ | |||
client.PublishAsync(message).GetAwaiter().GetResult(); | |||
sentMessagesCount++; | |||
} | |||
Console.WriteLine($"Sending {sentMessagesCount} messages per second. #" + (i + 1)); | |||
GC.Collect(); | |||
} | |||
} | |||
catch (Exception exception) | |||
{ | |||
Console.WriteLine(exception); | |||
} | |||
} | |||
public static void RunClientAndServer() | |||
{ | |||
try | |||
{ | |||
@@ -22,6 +22,8 @@ namespace MQTTnet.TestApp.NetCore | |||
Console.WriteLine("5 = Start public broker test"); | |||
Console.WriteLine("6 = Start server & client"); | |||
Console.WriteLine("7 = Client flow test"); | |||
Console.WriteLine("8 = Start performance test (client only)"); | |||
Console.WriteLine("9 = Start server (no trace)"); | |||
var pressedKey = Console.ReadKey(true); | |||
if (pressedKey.KeyChar == '1') | |||
@@ -34,7 +36,7 @@ namespace MQTTnet.TestApp.NetCore | |||
} | |||
else if (pressedKey.KeyChar == '3') | |||
{ | |||
PerformanceTest.Run(); | |||
PerformanceTest.RunClientAndServer(); | |||
return; | |||
} | |||
else if (pressedKey.KeyChar == '4') | |||
@@ -53,6 +55,16 @@ namespace MQTTnet.TestApp.NetCore | |||
{ | |||
Task.Run(ClientFlowTest.RunAsync); | |||
} | |||
else if (pressedKey.KeyChar == '8') | |||
{ | |||
PerformanceTest.RunClientOnly(); | |||
return; | |||
} | |||
else if (pressedKey.KeyChar == '9') | |||
{ | |||
ServerTest.RunEmptyServer(); | |||
return; | |||
} | |||
Thread.Sleep(Timeout.Infinite); | |||
} | |||
@@ -8,12 +8,19 @@ namespace MQTTnet.TestApp.NetCore | |||
{ | |||
public static class ServerTest | |||
{ | |||
public static void RunEmptyServer() | |||
{ | |||
var mqttServer = new MqttFactory().CreateMqttServer(); | |||
mqttServer.StartAsync(new MqttServerOptions()).GetAwaiter().GetResult(); | |||
Console.WriteLine("Press any key to exit."); | |||
Console.ReadLine(); | |||
} | |||
public static async Task RunAsync() | |||
{ | |||
try | |||
{ | |||
MqttNetConsoleLogger.ForwardToConsole(); | |||
var options = new MqttServerOptions | |||
{ | |||
ConnectionValidator = p => | |||