@@ -2,7 +2,7 @@ | |||||
<package > | <package > | ||||
<metadata> | <metadata> | ||||
<id>MQTTnet</id> | <id>MQTTnet</id> | ||||
<version>2.3.1</version> | |||||
<version>2.4.0</version> | |||||
<authors>Christian Kratky</authors> | <authors>Christian Kratky</authors> | ||||
<owners>Christian Kratky</owners> | <owners>Christian Kratky</owners> | ||||
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> | <licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> | ||||
@@ -10,10 +10,12 @@ | |||||
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> | <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> | ||||
<requireLicenseAcceptance>false</requireLicenseAcceptance> | <requireLicenseAcceptance>false</requireLicenseAcceptance> | ||||
<description>MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description> | <description>MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description> | ||||
<releaseNotes>* [Server] Fixed an issue when accepting a new connection (UWP only) (Thanks to haeberle) | |||||
[Core] Fixed a dead lock while sending messages (Thanks to 1liveowl, JanEggers) | |||||
[Client] The client is no longer sending packets before receiving has started | |||||
[Core] Minor changes and improvements | |||||
<releaseNotes>* [Server] Added an event which is fired when a client has disconnected. | |||||
* [Server] Added support for retained application messages | |||||
* [Server] Added support for saving and loading retained messages | |||||
* [Server] The client connection is now closed if sending of one pending application message has failed | |||||
* [Server] Fixed handling of _Dup_ flag (Thanks to haeberle) | |||||
* [Core] Optimized exception handling | |||||
</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</tags> | <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M</tags> | ||||
@@ -24,7 +24,7 @@ namespace MQTTnet.Implementations | |||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | ||||
public void Start(MqttServerOptions options) | |||||
public Task StartAsync(MqttServerOptions options) | |||||
{ | { | ||||
if (options == null) throw new ArgumentNullException(nameof(options)); | if (options == null) throw new ArgumentNullException(nameof(options)); | ||||
@@ -57,9 +57,11 @@ namespace MQTTnet.Implementations | |||||
Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); | Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); | ||||
} | } | ||||
return Task.FromResult(0); | |||||
} | } | ||||
public void Stop() | |||||
public Task StopAsync() | |||||
{ | { | ||||
_isRunning = false; | _isRunning = false; | ||||
@@ -72,11 +74,13 @@ namespace MQTTnet.Implementations | |||||
_tlsEndpointSocket?.Dispose(); | _tlsEndpointSocket?.Dispose(); | ||||
_tlsEndpointSocket = null; | _tlsEndpointSocket = null; | ||||
return Task.FromResult(0); | |||||
} | } | ||||
public void Dispose() | public void Dispose() | ||||
{ | { | ||||
Stop(); | |||||
StopAsync(); | |||||
} | } | ||||
private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken) | private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken) | ||||
@@ -24,7 +24,7 @@ namespace MQTTnet.Implementations | |||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | ||||
public void Start(MqttServerOptions options) | |||||
public Task StartAsync(MqttServerOptions options) | |||||
{ | { | ||||
if (_isRunning) throw new InvalidOperationException("Server is already started."); | if (_isRunning) throw new InvalidOperationException("Server is already started."); | ||||
@@ -56,9 +56,11 @@ namespace MQTTnet.Implementations | |||||
Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); | Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); | ||||
} | } | ||||
return Task.FromResult(0); | |||||
} | } | ||||
public void Stop() | |||||
public Task StopAsync() | |||||
{ | { | ||||
_isRunning = false; | _isRunning = false; | ||||
@@ -71,11 +73,13 @@ namespace MQTTnet.Implementations | |||||
_tlsEndpointSocket?.Dispose(); | _tlsEndpointSocket?.Dispose(); | ||||
_tlsEndpointSocket = null; | _tlsEndpointSocket = null; | ||||
return Task.FromResult(0); | |||||
} | } | ||||
public void Dispose() | public void Dispose() | ||||
{ | { | ||||
Stop(); | |||||
StopAsync(); | |||||
} | } | ||||
private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken) | private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken) | ||||
@@ -1,4 +1,5 @@ | |||||
using System; | using System; | ||||
using System.Threading.Tasks; | |||||
using MQTTnet.Core.Adapter; | using MQTTnet.Core.Adapter; | ||||
using MQTTnet.Core.Diagnostics; | using MQTTnet.Core.Diagnostics; | ||||
using MQTTnet.Core.Serializer; | using MQTTnet.Core.Serializer; | ||||
@@ -15,7 +16,7 @@ namespace MQTTnet.Implementations | |||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | ||||
public void Start(MqttServerOptions options) | |||||
public async Task StartAsync(MqttServerOptions options) | |||||
{ | { | ||||
if (options == null) throw new ArgumentNullException(nameof(options)); | if (options == null) throw new ArgumentNullException(nameof(options)); | ||||
@@ -26,7 +27,7 @@ namespace MQTTnet.Implementations | |||||
if (options.DefaultEndpointOptions.IsEnabled) | if (options.DefaultEndpointOptions.IsEnabled) | ||||
{ | { | ||||
_defaultEndpointSocket = new StreamSocketListener(); | _defaultEndpointSocket = new StreamSocketListener(); | ||||
_defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket).GetAwaiter().GetResult(); | |||||
await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket); | |||||
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync; | _defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync; | ||||
} | } | ||||
@@ -36,17 +37,19 @@ namespace MQTTnet.Implementations | |||||
} | } | ||||
} | } | ||||
public void Stop() | |||||
public Task StopAsync() | |||||
{ | { | ||||
_isRunning = false; | _isRunning = false; | ||||
_defaultEndpointSocket?.Dispose(); | _defaultEndpointSocket?.Dispose(); | ||||
_defaultEndpointSocket = null; | _defaultEndpointSocket = null; | ||||
return Task.FromResult(0); | |||||
} | } | ||||
public void Dispose() | public void Dispose() | ||||
{ | { | ||||
Stop(); | |||||
StopAsync(); | |||||
} | } | ||||
private void AcceptDefaultEndpointConnectionsAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) | private void AcceptDefaultEndpointConnectionsAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) | ||||
@@ -1,4 +1,5 @@ | |||||
using System; | using System; | ||||
using System.Threading.Tasks; | |||||
using MQTTnet.Core.Server; | using MQTTnet.Core.Server; | ||||
namespace MQTTnet.Core.Adapter | namespace MQTTnet.Core.Adapter | ||||
@@ -7,8 +8,7 @@ namespace MQTTnet.Core.Adapter | |||||
{ | { | ||||
event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | ||||
void Start(MqttServerOptions options); | |||||
void Stop(); | |||||
Task StartAsync(MqttServerOptions options); | |||||
Task StopAsync(); | |||||
} | } | ||||
} | } |
@@ -36,6 +36,10 @@ namespace MQTTnet.Core.Adapter | |||||
{ | { | ||||
throw; | throw; | ||||
} | } | ||||
catch (OperationCanceledException) | |||||
{ | |||||
throw; | |||||
} | |||||
catch (MqttCommunicationTimedOutException) | catch (MqttCommunicationTimedOutException) | ||||
{ | { | ||||
throw; | throw; | ||||
@@ -60,6 +64,10 @@ namespace MQTTnet.Core.Adapter | |||||
{ | { | ||||
throw; | throw; | ||||
} | } | ||||
catch (OperationCanceledException) | |||||
{ | |||||
throw; | |||||
} | |||||
catch (MqttCommunicationTimedOutException) | catch (MqttCommunicationTimedOutException) | ||||
{ | { | ||||
throw; | throw; | ||||
@@ -87,7 +95,7 @@ namespace MQTTnet.Core.Adapter | |||||
continue; | continue; | ||||
} | } | ||||
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"TX >>> {0} [Timeout={1}]", packet, timeout); | |||||
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), "TX >>> {0} [Timeout={1}]", packet, timeout); | |||||
var writeBuffer = PacketSerializer.Serialize(packet); | var writeBuffer = PacketSerializer.Serialize(packet); | ||||
await _channel.SendStream.WriteAsync(writeBuffer, 0, writeBuffer.Length, cancellationToken).ConfigureAwait(false); | await _channel.SendStream.WriteAsync(writeBuffer, 0, writeBuffer.Length, cancellationToken).ConfigureAwait(false); | ||||
@@ -106,6 +114,10 @@ namespace MQTTnet.Core.Adapter | |||||
{ | { | ||||
throw; | throw; | ||||
} | } | ||||
catch (OperationCanceledException) | |||||
{ | |||||
throw; | |||||
} | |||||
catch (MqttCommunicationTimedOutException) | catch (MqttCommunicationTimedOutException) | ||||
{ | { | ||||
throw; | throw; | ||||
@@ -156,6 +168,10 @@ namespace MQTTnet.Core.Adapter | |||||
{ | { | ||||
throw; | throw; | ||||
} | } | ||||
catch (OperationCanceledException) | |||||
{ | |||||
throw; | |||||
} | |||||
catch (MqttCommunicationTimedOutException) | catch (MqttCommunicationTimedOutException) | ||||
{ | { | ||||
throw; | throw; | ||||
@@ -0,0 +1,17 @@ | |||||
using System; | |||||
namespace MQTTnet.Core.Adapter | |||||
{ | |||||
public class MqttClientDisconnectedEventArgs : EventArgs | |||||
{ | |||||
public MqttClientDisconnectedEventArgs(string identifier, IMqttCommunicationAdapter clientAdapter) | |||||
{ | |||||
Identifier = identifier ?? throw new ArgumentNullException(nameof(identifier)); | |||||
ClientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter)); | |||||
} | |||||
public string Identifier { get; } | |||||
public IMqttCommunicationAdapter ClientAdapter { get; } | |||||
} | |||||
} |
@@ -370,7 +370,7 @@ namespace MQTTnet.Core.Client | |||||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false); | await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false); | ||||
} | } | ||||
} | } | ||||
catch (TaskCanceledException) | |||||
catch (OperationCanceledException) | |||||
{ | { | ||||
} | } | ||||
catch (MqttCommunicationException exception) | catch (MqttCommunicationException exception) | ||||
@@ -413,7 +413,7 @@ namespace MQTTnet.Core.Client | |||||
StartProcessReceivedPacket(packet, cancellationToken); | StartProcessReceivedPacket(packet, cancellationToken); | ||||
} | } | ||||
} | } | ||||
catch (TaskCanceledException) | |||||
catch (OperationCanceledException) | |||||
{ | { | ||||
} | } | ||||
catch (MqttCommunicationException exception) | catch (MqttCommunicationException exception) | ||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Core.Packets | |||||
public override string ToString() | public override string ToString() | ||||
{ | { | ||||
var topicFiltersText = string.Join(",", TopicFilters.Select(f => $"{f.Topic}@{f.QualityOfServiceLevel}")); | |||||
var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel)); | |||||
return nameof(MqttSubscribePacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; | return nameof(MqttSubscribePacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; | ||||
} | } | ||||
} | } | ||||
@@ -212,8 +212,7 @@ namespace MQTTnet.Core.Serializer | |||||
default: | default: | ||||
{ | { | ||||
throw new MqttProtocolViolationException( | |||||
$"Packet type ({(int)header.ControlPacketType}) not supported."); | |||||
throw new MqttProtocolViolationException($"Packet type ({(int)header.ControlPacketType}) not supported."); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -1,5 +1,6 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Threading.Tasks; | |||||
using MQTTnet.Core.Adapter; | using MQTTnet.Core.Adapter; | ||||
namespace MQTTnet.Core.Server | namespace MQTTnet.Core.Server | ||||
@@ -12,7 +13,8 @@ namespace MQTTnet.Core.Server | |||||
IList<ConnectedMqttClient> GetConnectedClients(); | IList<ConnectedMqttClient> GetConnectedClients(); | ||||
void InjectClient(string identifier, IMqttCommunicationAdapter adapter); | void InjectClient(string identifier, IMqttCommunicationAdapter adapter); | ||||
void Publish(MqttApplicationMessage applicationMessage); | void Publish(MqttApplicationMessage applicationMessage); | ||||
void Start(); | |||||
void Stop(); | |||||
Task StartAsync(); | |||||
Task StopAsync(); | |||||
} | } | ||||
} | } |
@@ -0,0 +1,12 @@ | |||||
using System.Collections.Generic; | |||||
using System.Threading.Tasks; | |||||
namespace MQTTnet.Core.Server | |||||
{ | |||||
public interface IMqttServerStorage | |||||
{ | |||||
Task SaveRetainedMessagesAsync(IList<MqttApplicationMessage> messages); | |||||
Task<IList<MqttApplicationMessage>> LoadRetainedMessagesAsync(); | |||||
} | |||||
} |
@@ -1,89 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using MQTTnet.Core.Adapter; | |||||
using MQTTnet.Core.Diagnostics; | |||||
using MQTTnet.Core.Exceptions; | |||||
using MQTTnet.Core.Packets; | |||||
using System.Linq; | |||||
namespace MQTTnet.Core.Server | |||||
{ | |||||
public sealed class MqttClientMessageQueue | |||||
{ | |||||
private readonly BlockingCollection<MqttPublishPacket> _pendingPublishPackets = new BlockingCollection<MqttPublishPacket>(); | |||||
private readonly MqttServerOptions _options; | |||||
private CancellationTokenSource _cancellationTokenSource; | |||||
public MqttClientMessageQueue(MqttServerOptions options) | |||||
{ | |||||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||||
} | |||||
public void Start(IMqttCommunicationAdapter adapter) | |||||
{ | |||||
if (_cancellationTokenSource != null) | |||||
{ | |||||
throw new InvalidOperationException($"{nameof(MqttClientMessageQueue)} already started."); | |||||
} | |||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter)); | |||||
_cancellationTokenSource = new CancellationTokenSource(); | |||||
Task.Run(() => SendPendingPublishPacketsAsync(_cancellationTokenSource.Token, adapter), _cancellationTokenSource.Token); | |||||
} | |||||
public void Stop() | |||||
{ | |||||
_cancellationTokenSource?.Cancel(); | |||||
_cancellationTokenSource = null; | |||||
_pendingPublishPackets?.Dispose(); | |||||
} | |||||
public void Enqueue(MqttPublishPacket publishPacket) | |||||
{ | |||||
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); | |||||
_pendingPublishPackets.Add(publishPacket); | |||||
} | |||||
private async Task SendPendingPublishPacketsAsync(CancellationToken cancellationToken, IMqttCommunicationAdapter adapter) | |||||
{ | |||||
var consumable = _pendingPublishPackets.GetConsumingEnumerable(); | |||||
while (!cancellationToken.IsCancellationRequested) | |||||
{ | |||||
if (_pendingPublishPackets.Count == 0) | |||||
{ | |||||
await Task.Delay(TimeSpan.FromMilliseconds(5), cancellationToken).ConfigureAwait(false); | |||||
continue; | |||||
} | |||||
var packets = consumable.Take(_pendingPublishPackets.Count).ToList(); | |||||
try | |||||
{ | |||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packets).ConfigureAwait(false); | |||||
} | |||||
catch (MqttCommunicationException exception) | |||||
{ | |||||
MqttTrace.Warning(nameof(MqttClientMessageQueue), exception, "Sending publish packet failed."); | |||||
foreach (var publishPacket in packets) | |||||
{ | |||||
publishPacket.Dup = true; | |||||
_pendingPublishPackets.Add(publishPacket, cancellationToken); | |||||
} | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
MqttTrace.Error(nameof(MqttClientMessageQueue), exception, "Sending publish packet failed."); | |||||
foreach (var publishPacket in packets) | |||||
{ | |||||
publishPacket.Dup = true; | |||||
_pendingPublishPackets.Add(publishPacket, cancellationToken); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,90 @@ | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using MQTTnet.Core.Adapter; | |||||
using MQTTnet.Core.Diagnostics; | |||||
using MQTTnet.Core.Exceptions; | |||||
using MQTTnet.Core.Packets; | |||||
using MQTTnet.Core.Protocol; | |||||
namespace MQTTnet.Core.Server | |||||
{ | |||||
public sealed class MqttClientPendingMessagesQueue | |||||
{ | |||||
private readonly BlockingCollection<MqttPublishPacket> _pendingPublishPackets = new BlockingCollection<MqttPublishPacket>(); | |||||
private readonly MqttClientSession _session; | |||||
private readonly MqttServerOptions _options; | |||||
public MqttClientPendingMessagesQueue(MqttServerOptions options, MqttClientSession session) | |||||
{ | |||||
_session = session ?? throw new ArgumentNullException(nameof(session)); | |||||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||||
} | |||||
public void Start(IMqttCommunicationAdapter adapter, CancellationToken cancellationToken) | |||||
{ | |||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter)); | |||||
Task.Run(() => SendPendingPublishPacketsAsync(adapter, cancellationToken), cancellationToken); | |||||
} | |||||
public void Enqueue(MqttPublishPacket publishPacket) | |||||
{ | |||||
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); | |||||
_pendingPublishPackets.Add(publishPacket); | |||||
} | |||||
private async Task SendPendingPublishPacketsAsync(IMqttCommunicationAdapter adapter, CancellationToken cancellationToken) | |||||
{ | |||||
try | |||||
{ | |||||
while (!cancellationToken.IsCancellationRequested) | |||||
{ | |||||
await SendPendingPublishPacketAsync(adapter, cancellationToken); | |||||
} | |||||
} | |||||
catch (OperationCanceledException) | |||||
{ | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
MqttTrace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Unhandled exception while sending pending publish packets."); | |||||
} | |||||
} | |||||
private async Task SendPendingPublishPacketAsync(IMqttCommunicationAdapter adapter, CancellationToken cancellationToken) | |||||
{ | |||||
var packet = _pendingPublishPackets.Take(cancellationToken); | |||||
try | |||||
{ | |||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packet).ConfigureAwait(false); | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
if (exception is MqttCommunicationTimedOutException) | |||||
{ | |||||
MqttTrace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to timeout."); | |||||
} | |||||
else if (exception is MqttCommunicationException) | |||||
{ | |||||
MqttTrace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to communication exception."); | |||||
} | |||||
else | |||||
{ | |||||
MqttTrace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed."); | |||||
} | |||||
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) | |||||
{ | |||||
packet.Dup = true; | |||||
_pendingPublishPackets.Add(packet, cancellationToken); | |||||
} | |||||
_session.Stop(); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,104 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using MQTTnet.Core.Diagnostics; | |||||
using MQTTnet.Core.Internal; | |||||
using MQTTnet.Core.Packets; | |||||
namespace MQTTnet.Core.Server | |||||
{ | |||||
public sealed class MqttClientRetainedMessagesManager | |||||
{ | |||||
private readonly Dictionary<string, MqttPublishPacket> _retainedMessages = new Dictionary<string, MqttPublishPacket>(); | |||||
private readonly MqttServerOptions _options; | |||||
public MqttClientRetainedMessagesManager(MqttServerOptions options) | |||||
{ | |||||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||||
} | |||||
public async Task LoadMessagesAsync() | |||||
{ | |||||
try | |||||
{ | |||||
var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync(); | |||||
lock (_retainedMessages) | |||||
{ | |||||
_retainedMessages.Clear(); | |||||
foreach (var retainedMessage in retainedMessages) | |||||
{ | |||||
_retainedMessages[retainedMessage.Topic] = retainedMessage.ToPublishPacket(); | |||||
} | |||||
} | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
MqttTrace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while loading retained messages."); | |||||
} | |||||
} | |||||
public async Task HandleMessageAsync(string clientId, MqttPublishPacket publishPacket) | |||||
{ | |||||
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); | |||||
List<MqttPublishPacket> allRetainedMessages; | |||||
lock (_retainedMessages) | |||||
{ | |||||
if (publishPacket.Payload?.Any() == false) | |||||
{ | |||||
_retainedMessages.Remove(publishPacket.Topic); | |||||
MqttTrace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' cleared retained message for topic '{1}'.", clientId, publishPacket.Topic); | |||||
} | |||||
else | |||||
{ | |||||
_retainedMessages[publishPacket.Topic] = publishPacket; | |||||
MqttTrace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' updated retained message for topic '{1}'.", clientId, publishPacket.Topic); | |||||
} | |||||
allRetainedMessages = new List<MqttPublishPacket>(_retainedMessages.Values); | |||||
} | |||||
try | |||||
{ | |||||
// ReSharper disable once UseNullPropagation | |||||
if (_options.Storage != null) | |||||
{ | |||||
await _options.Storage.SaveRetainedMessagesAsync(allRetainedMessages.Select(p => p.ToApplicationMessage()).ToList()); | |||||
} | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
MqttTrace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while saving retained messages."); | |||||
} | |||||
} | |||||
public List<MqttPublishPacket> GetMessages(MqttSubscribePacket subscribePacket) | |||||
{ | |||||
var retainedMessages = new List<MqttPublishPacket>(); | |||||
lock (_retainedMessages) | |||||
{ | |||||
foreach (var retainedMessage in _retainedMessages.Values) | |||||
{ | |||||
foreach (var topicFilter in subscribePacket.TopicFilters) | |||||
{ | |||||
if (retainedMessage.QualityOfServiceLevel < topicFilter.QualityOfServiceLevel) | |||||
{ | |||||
continue; | |||||
} | |||||
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic)) | |||||
{ | |||||
continue; | |||||
} | |||||
retainedMessages.Add(retainedMessage); | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
return retainedMessages; | |||||
} | |||||
} | |||||
} |
@@ -14,23 +14,22 @@ namespace MQTTnet.Core.Server | |||||
public sealed class MqttClientSession : IDisposable | public sealed class MqttClientSession : IDisposable | ||||
{ | { | ||||
private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>(); | private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>(); | ||||
private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager(); | private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager(); | ||||
private readonly MqttClientMessageQueue _messageQueue; | |||||
private readonly Action<MqttClientSession, MqttPublishPacket> _publishPacketReceivedCallback; | |||||
private readonly MqttClientSessionsManager _mqttClientSessionsManager; | |||||
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue; | |||||
private readonly MqttServerOptions _options; | private readonly MqttServerOptions _options; | ||||
private CancellationTokenSource _cancellationTokenSource; | |||||
private string _identifier; | private string _identifier; | ||||
private MqttApplicationMessage _willApplicationMessage; | |||||
private CancellationTokenSource _cancellationTokenSource; | |||||
private MqttApplicationMessage _willMessage; | |||||
public MqttClientSession(string clientId, MqttServerOptions options, Action<MqttClientSession, MqttPublishPacket> publishPacketReceivedCallback) | |||||
public MqttClientSession(string clientId, MqttServerOptions options, MqttClientSessionsManager mqttClientSessionsManager) | |||||
{ | { | ||||
ClientId = clientId; | ClientId = clientId; | ||||
_options = options ?? throw new ArgumentNullException(nameof(options)); | _options = options ?? throw new ArgumentNullException(nameof(options)); | ||||
_publishPacketReceivedCallback = publishPacketReceivedCallback ?? throw new ArgumentNullException(nameof(publishPacketReceivedCallback)); | |||||
_messageQueue = new MqttClientMessageQueue(options); | |||||
_mqttClientSessionsManager = mqttClientSessionsManager ?? throw new ArgumentNullException(nameof(mqttClientSessionsManager)); | |||||
_pendingMessagesQueue = new MqttClientPendingMessagesQueue(options, this); | |||||
} | } | ||||
public string ClientId { get; } | public string ClientId { get; } | ||||
@@ -39,11 +38,11 @@ namespace MQTTnet.Core.Server | |||||
public IMqttCommunicationAdapter Adapter { get; private set; } | public IMqttCommunicationAdapter Adapter { get; private set; } | ||||
public async Task RunAsync(string identifier, MqttApplicationMessage willApplicationMessage, IMqttCommunicationAdapter adapter) | |||||
public async Task RunAsync(string identifier, MqttApplicationMessage willMessage, IMqttCommunicationAdapter adapter) | |||||
{ | { | ||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter)); | if (adapter == null) throw new ArgumentNullException(nameof(adapter)); | ||||
_willApplicationMessage = willApplicationMessage; | |||||
_willMessage = willMessage; | |||||
try | try | ||||
{ | { | ||||
@@ -51,33 +50,36 @@ namespace MQTTnet.Core.Server | |||||
Adapter = adapter; | Adapter = adapter; | ||||
_cancellationTokenSource = new CancellationTokenSource(); | _cancellationTokenSource = new CancellationTokenSource(); | ||||
_messageQueue.Start(adapter); | |||||
while (!_cancellationTokenSource.IsCancellationRequested) | |||||
{ | |||||
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false); | |||||
await HandleIncomingPacketAsync(packet).ConfigureAwait(false); | |||||
} | |||||
_pendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token); | |||||
await ReceivePacketsAsync(adapter, _cancellationTokenSource.Token); | |||||
} | |||||
catch (OperationCanceledException) | |||||
{ | |||||
} | } | ||||
catch (MqttCommunicationException) | |||||
catch (MqttCommunicationException exception) | |||||
{ | { | ||||
MqttTrace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", _identifier); | |||||
} | } | ||||
catch (Exception exception) | catch (Exception exception) | ||||
{ | { | ||||
MqttTrace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", _identifier); | MqttTrace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", _identifier); | ||||
} | } | ||||
finally | |||||
} | |||||
public void Stop() | |||||
{ | |||||
if (_willMessage != null) | |||||
{ | { | ||||
if (willApplicationMessage != null) | |||||
{ | |||||
_publishPacketReceivedCallback(this, _willApplicationMessage.ToPublishPacket()); | |||||
} | |||||
_mqttClientSessionsManager.DispatchPublishPacket(this, _willMessage.ToPublishPacket()); | |||||
} | |||||
_messageQueue.Stop(); | |||||
_cancellationTokenSource.Cancel(); | |||||
Adapter = null; | |||||
_cancellationTokenSource?.Cancel(false); | |||||
_cancellationTokenSource?.Dispose(); | |||||
_cancellationTokenSource = null; | |||||
MqttTrace.Information(nameof(MqttClientSession), "Client '{0}': Disconnected.", _identifier); | |||||
} | |||||
Adapter = null; | |||||
MqttTrace.Information(nameof(MqttClientSession), "Client '{0}': Disconnected.", _identifier); | |||||
} | } | ||||
public void EnqueuePublishPacket(MqttPublishPacket publishPacket) | public void EnqueuePublishPacket(MqttPublishPacket publishPacket) | ||||
@@ -89,7 +91,7 @@ namespace MQTTnet.Core.Server | |||||
return; | return; | ||||
} | } | ||||
_messageQueue.Enqueue(publishPacket); | |||||
_pendingMessagesQueue.Enqueue(publishPacket); | |||||
MqttTrace.Verbose(nameof(MqttClientSession), "Client '{0}': Enqueued pending publish packet.", _identifier); | MqttTrace.Verbose(nameof(MqttClientSession), "Client '{0}': Enqueued pending publish packet.", _identifier); | ||||
} | } | ||||
@@ -99,68 +101,100 @@ namespace MQTTnet.Core.Server | |||||
_cancellationTokenSource?.Dispose(); | _cancellationTokenSource?.Dispose(); | ||||
} | } | ||||
private Task HandleIncomingPacketAsync(MqttBasePacket packet) | |||||
private async Task ReceivePacketsAsync(IMqttCommunicationAdapter adapter, CancellationToken cancellationToken) | |||||
{ | { | ||||
if (packet is MqttSubscribePacket subscribePacket) | |||||
try | |||||
{ | { | ||||
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, _subscriptionsManager.Subscribe(subscribePacket)); | |||||
while (!cancellationToken.IsCancellationRequested) | |||||
{ | |||||
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); | |||||
await ProcessReceivedPacketAsync(packet).ConfigureAwait(false); | |||||
} | |||||
} | } | ||||
if (packet is MqttUnsubscribePacket unsubscribePacket) | |||||
catch (OperationCanceledException) | |||||
{ | { | ||||
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, _subscriptionsManager.Unsubscribe(unsubscribePacket)); | |||||
} | } | ||||
if (packet is MqttPublishPacket publishPacket) | |||||
catch (MqttCommunicationException exception) | |||||
{ | { | ||||
return HandleIncomingPublishPacketAsync(publishPacket); | |||||
MqttTrace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", _identifier); | |||||
Stop(); | |||||
} | } | ||||
if (packet is MqttPubRelPacket pubRelPacket) | |||||
catch (Exception exception) | |||||
{ | { | ||||
return HandleIncomingPubRelPacketAsync(pubRelPacket); | |||||
MqttTrace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", _identifier); | |||||
Stop(); | |||||
} | } | ||||
} | |||||
if (packet is MqttPubRecPacket pubRecPacket) | |||||
private async Task ProcessReceivedPacketAsync(MqttBasePacket packet) | |||||
{ | |||||
if (packet is MqttSubscribePacket subscribePacket) | |||||
{ | { | ||||
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, pubRecPacket.CreateResponse<MqttPubRelPacket>()); | |||||
await Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, _subscriptionsManager.Subscribe(subscribePacket)); | |||||
EnqueueRetainedMessages(subscribePacket); | |||||
} | } | ||||
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) | |||||
else if (packet is MqttUnsubscribePacket unsubscribePacket) | |||||
{ | |||||
await Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, _subscriptionsManager.Unsubscribe(unsubscribePacket)); | |||||
} | |||||
else if (packet is MqttPublishPacket publishPacket) | |||||
{ | |||||
await HandleIncomingPublishPacketAsync(publishPacket); | |||||
} | |||||
else if (packet is MqttPubRelPacket pubRelPacket) | |||||
{ | |||||
await HandleIncomingPubRelPacketAsync(pubRelPacket); | |||||
} | |||||
else if (packet is MqttPubRecPacket pubRecPacket) | |||||
{ | |||||
await Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, pubRecPacket.CreateResponse<MqttPubRelPacket>()); | |||||
} | |||||
else if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) | |||||
{ | { | ||||
// Discard message. | // Discard message. | ||||
return Task.FromResult((object)null); | |||||
} | } | ||||
if (packet is MqttPingReqPacket) | |||||
else if (packet is MqttPingReqPacket) | |||||
{ | { | ||||
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPingRespPacket()); | |||||
await Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPingRespPacket()); | |||||
} | } | ||||
if (packet is MqttDisconnectPacket || packet is MqttConnectPacket) | |||||
else if (packet is MqttDisconnectPacket || packet is MqttConnectPacket) | |||||
{ | { | ||||
_cancellationTokenSource.Cancel(); | _cancellationTokenSource.Cancel(); | ||||
return Task.FromResult((object)null); | |||||
} | } | ||||
else | |||||
{ | |||||
MqttTrace.Warning(nameof(MqttClientSession), "Client '{0}': Received not supported packet ({1}). Closing connection.", _identifier, packet); | |||||
_cancellationTokenSource.Cancel(); | |||||
} | |||||
} | |||||
MqttTrace.Warning(nameof(MqttClientSession), "Client '{0}': Received not supported packet ({1}). Closing connection.", _identifier, packet); | |||||
_cancellationTokenSource.Cancel(); | |||||
return Task.FromResult((object)null); | |||||
private void EnqueueRetainedMessages(MqttSubscribePacket subscribePacket) | |||||
{ | |||||
var retainedMessages = _mqttClientSessionsManager.RetainedMessagesManager.GetMessages(subscribePacket); | |||||
foreach (var publishPacket in retainedMessages) | |||||
{ | |||||
EnqueuePublishPacket(publishPacket); | |||||
} | |||||
} | } | ||||
private Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket) | |||||
private async Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket) | |||||
{ | { | ||||
if (publishPacket.Retain) | |||||
{ | |||||
await _mqttClientSessionsManager.RetainedMessagesManager.HandleMessageAsync(_identifier, publishPacket); | |||||
} | |||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) | if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) | ||||
{ | { | ||||
_publishPacketReceivedCallback(this, publishPacket); | |||||
return Task.FromResult(0); | |||||
_mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); | |||||
return; | |||||
} | } | ||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) | if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) | ||||
{ | { | ||||
_publishPacketReceivedCallback(this, publishPacket); | |||||
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }); | |||||
_mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); | |||||
await Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }); | |||||
return; | |||||
} | } | ||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) | if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) | ||||
@@ -171,9 +205,10 @@ namespace MQTTnet.Core.Server | |||||
_unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier); | _unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier); | ||||
} | } | ||||
_publishPacketReceivedCallback(this, publishPacket); | |||||
_mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket); | |||||
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }); | |||||
await Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }); | |||||
return; | |||||
} | } | ||||
throw new MqttCommunicationException("Received a not supported QoS level."); | throw new MqttCommunicationException("Received a not supported QoS level."); | ||||
@@ -14,19 +14,24 @@ namespace MQTTnet.Core.Server | |||||
{ | { | ||||
public sealed class MqttClientSessionsManager | public sealed class MqttClientSessionsManager | ||||
{ | { | ||||
private readonly object _syncRoot = new object(); | |||||
private readonly Dictionary<string, MqttClientSession> _clientSessions = new Dictionary<string, MqttClientSession>(); | private readonly Dictionary<string, MqttClientSession> _clientSessions = new Dictionary<string, MqttClientSession>(); | ||||
private readonly MqttServerOptions _options; | private readonly MqttServerOptions _options; | ||||
public MqttClientSessionsManager(MqttServerOptions options) | public MqttClientSessionsManager(MqttServerOptions options) | ||||
{ | { | ||||
_options = options ?? throw new ArgumentNullException(nameof(options)); | _options = options ?? throw new ArgumentNullException(nameof(options)); | ||||
RetainedMessagesManager = new MqttClientRetainedMessagesManager(options); | |||||
} | } | ||||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; | public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; | ||||
public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected; | |||||
public MqttClientRetainedMessagesManager RetainedMessagesManager { get; } | |||||
public async Task RunClientSessionAsync(MqttClientConnectedEventArgs eventArgs) | public async Task RunClientSessionAsync(MqttClientConnectedEventArgs eventArgs) | ||||
{ | { | ||||
var clientId = string.Empty; | |||||
try | try | ||||
{ | { | ||||
if (!(await eventArgs.ClientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false) is MqttConnectPacket connectPacket)) | if (!(await eventArgs.ClientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false) is MqttConnectPacket connectPacket)) | ||||
@@ -34,9 +39,11 @@ namespace MQTTnet.Core.Server | |||||
throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); | throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); | ||||
} | } | ||||
clientId = connectPacket.ClientId; | |||||
// Switch to the required protocol version before sending any response. | // Switch to the required protocol version before sending any response. | ||||
eventArgs.ClientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion; | eventArgs.ClientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion; | ||||
var connectReturnCode = ValidateConnection(connectPacket); | var connectReturnCode = ValidateConnection(connectPacket); | ||||
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) | if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) | ||||
{ | { | ||||
@@ -65,12 +72,13 @@ namespace MQTTnet.Core.Server | |||||
finally | finally | ||||
{ | { | ||||
await eventArgs.ClientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); | await eventArgs.ClientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); | ||||
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientId, eventArgs.ClientAdapter)); | |||||
} | } | ||||
} | } | ||||
public void Clear() | public void Clear() | ||||
{ | { | ||||
lock (_syncRoot) | |||||
lock (_clientSessions) | |||||
{ | { | ||||
_clientSessions.Clear(); | _clientSessions.Clear(); | ||||
} | } | ||||
@@ -78,7 +86,7 @@ namespace MQTTnet.Core.Server | |||||
public IList<ConnectedMqttClient> GetConnectedClients() | public IList<ConnectedMqttClient> GetConnectedClients() | ||||
{ | { | ||||
lock (_syncRoot) | |||||
lock (_clientSessions) | |||||
{ | { | ||||
return _clientSessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient | return _clientSessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient | ||||
{ | { | ||||
@@ -88,6 +96,27 @@ namespace MQTTnet.Core.Server | |||||
} | } | ||||
} | } | ||||
public void DispatchPublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket) | |||||
{ | |||||
try | |||||
{ | |||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, publishPacket.ToApplicationMessage()); | |||||
ApplicationMessageReceived?.Invoke(this, eventArgs); | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
MqttTrace.Error(nameof(MqttClientSessionsManager), exception, "Error while processing application message"); | |||||
} | |||||
lock (_clientSessions) | |||||
{ | |||||
foreach (var clientSession in _clientSessions.Values.ToList()) | |||||
{ | |||||
clientSession.EnqueuePublishPacket(publishPacket); | |||||
} | |||||
} | |||||
} | |||||
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) | private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) | ||||
{ | { | ||||
if (_options.ConnectionValidator != null) | if (_options.ConnectionValidator != null) | ||||
@@ -100,10 +129,9 @@ namespace MQTTnet.Core.Server | |||||
private GetOrCreateClientSessionResult GetOrCreateClientSession(MqttConnectPacket connectPacket) | private GetOrCreateClientSessionResult GetOrCreateClientSession(MqttConnectPacket connectPacket) | ||||
{ | { | ||||
lock (_syncRoot) | |||||
lock (_clientSessions) | |||||
{ | { | ||||
MqttClientSession clientSession; | |||||
var isSessionPresent = _clientSessions.TryGetValue(connectPacket.ClientId, out clientSession); | |||||
var isSessionPresent = _clientSessions.TryGetValue(connectPacket.ClientId, out var clientSession); | |||||
if (isSessionPresent) | if (isSessionPresent) | ||||
{ | { | ||||
if (connectPacket.CleanSession) | if (connectPacket.CleanSession) | ||||
@@ -124,7 +152,7 @@ namespace MQTTnet.Core.Server | |||||
{ | { | ||||
isExistingSession = false; | isExistingSession = false; | ||||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, DispatchPublishPacket); | |||||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, this); | |||||
_clientSessions[connectPacket.ClientId] = clientSession; | _clientSessions[connectPacket.ClientId] = clientSession; | ||||
MqttTrace.Verbose(nameof(MqttClientSessionsManager), "Created a new session for client '{0}'.", connectPacket.ClientId); | MqttTrace.Verbose(nameof(MqttClientSessionsManager), "Created a new session for client '{0}'.", connectPacket.ClientId); | ||||
@@ -133,26 +161,5 @@ namespace MQTTnet.Core.Server | |||||
return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession }; | return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession }; | ||||
} | } | ||||
} | } | ||||
public void DispatchPublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket) | |||||
{ | |||||
try | |||||
{ | |||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, publishPacket.ToApplicationMessage()); | |||||
ApplicationMessageReceived?.Invoke(this, eventArgs); | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
MqttTrace.Error(nameof(MqttClientSessionsManager), exception, "Error while processing application message"); | |||||
} | |||||
lock (_syncRoot) | |||||
{ | |||||
foreach (var clientSession in _clientSessions.Values.ToList()) | |||||
{ | |||||
clientSession.EnqueuePublishPacket(publishPacket); | |||||
} | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
@@ -23,6 +23,7 @@ namespace MQTTnet.Core.Server | |||||
_clientSessionsManager = new MqttClientSessionsManager(options); | _clientSessionsManager = new MqttClientSessionsManager(options); | ||||
_clientSessionsManager.ApplicationMessageReceived += (s, e) => ApplicationMessageReceived?.Invoke(s, e); | _clientSessionsManager.ApplicationMessageReceived += (s, e) => ApplicationMessageReceived?.Invoke(s, e); | ||||
_clientSessionsManager.ClientDisconnected += OnClientDisconnected; | |||||
} | } | ||||
public IList<ConnectedMqttClient> GetConnectedClients() | public IList<ConnectedMqttClient> GetConnectedClients() | ||||
@@ -31,7 +32,7 @@ namespace MQTTnet.Core.Server | |||||
} | } | ||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | ||||
public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected; | |||||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; | public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; | ||||
public void Publish(MqttApplicationMessage applicationMessage) | public void Publish(MqttApplicationMessage applicationMessage) | ||||
@@ -44,28 +45,29 @@ namespace MQTTnet.Core.Server | |||||
public void InjectClient(string identifier, IMqttCommunicationAdapter adapter) | public void InjectClient(string identifier, IMqttCommunicationAdapter adapter) | ||||
{ | { | ||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter)); | if (adapter == null) throw new ArgumentNullException(nameof(adapter)); | ||||
if (_cancellationTokenSource == null) throw new InvalidOperationException("The MQTT server is not started."); | if (_cancellationTokenSource == null) throw new InvalidOperationException("The MQTT server is not started."); | ||||
OnClientConnected(this, new MqttClientConnectedEventArgs(identifier, adapter)); | OnClientConnected(this, new MqttClientConnectedEventArgs(identifier, adapter)); | ||||
} | } | ||||
public void Start() | |||||
public async Task StartAsync() | |||||
{ | { | ||||
if (_cancellationTokenSource != null) throw new InvalidOperationException("The MQTT server is already started."); | if (_cancellationTokenSource != null) throw new InvalidOperationException("The MQTT server is already started."); | ||||
_cancellationTokenSource = new CancellationTokenSource(); | _cancellationTokenSource = new CancellationTokenSource(); | ||||
await _clientSessionsManager.RetainedMessagesManager.LoadMessagesAsync(); | |||||
foreach (var adapter in _adapters) | foreach (var adapter in _adapters) | ||||
{ | { | ||||
adapter.ClientConnected += OnClientConnected; | adapter.ClientConnected += OnClientConnected; | ||||
adapter.Start(_options); | |||||
await adapter.StartAsync(_options); | |||||
} | } | ||||
MqttTrace.Information(nameof(MqttServer), "Started."); | MqttTrace.Information(nameof(MqttServer), "Started."); | ||||
} | } | ||||
public void Stop() | |||||
public async Task StopAsync() | |||||
{ | { | ||||
_cancellationTokenSource?.Cancel(false); | _cancellationTokenSource?.Cancel(false); | ||||
_cancellationTokenSource?.Dispose(); | _cancellationTokenSource?.Dispose(); | ||||
@@ -74,7 +76,7 @@ namespace MQTTnet.Core.Server | |||||
foreach (var adapter in _adapters) | foreach (var adapter in _adapters) | ||||
{ | { | ||||
adapter.ClientConnected -= OnClientConnected; | adapter.ClientConnected -= OnClientConnected; | ||||
adapter.Stop(); | |||||
await adapter.StopAsync(); | |||||
} | } | ||||
_clientSessionsManager.Clear(); | _clientSessionsManager.Clear(); | ||||
@@ -89,5 +91,11 @@ namespace MQTTnet.Core.Server | |||||
Task.Run(() => _clientSessionsManager.RunClientSessionAsync(eventArgs), _cancellationTokenSource.Token); | Task.Run(() => _clientSessionsManager.RunClientSessionAsync(eventArgs), _cancellationTokenSource.Token); | ||||
} | } | ||||
private void OnClientDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs) | |||||
{ | |||||
MqttTrace.Information(nameof(MqttServer), "Client '{0}': Disconnected.", eventArgs.Identifier); | |||||
ClientDisconnected?.Invoke(this, eventArgs); | |||||
} | |||||
} | } | ||||
} | } |
@@ -15,5 +15,7 @@ namespace MQTTnet.Core.Server | |||||
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15); | public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15); | ||||
public Func<MqttConnectPacket, MqttConnectReturnCode> ConnectionValidator { get; set; } | public Func<MqttConnectPacket, MqttConnectReturnCode> ConnectionValidator { get; set; } | ||||
public IMqttServerStorage Storage { get; set; } | |||||
} | } | ||||
} | } |
@@ -49,7 +49,7 @@ namespace MQTTnet.Core.Tests | |||||
public async Task MqttServer_WillMessage() | public async Task MqttServer_WillMessage() | ||||
{ | { | ||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); | var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); | ||||
s.Start(); | |||||
s.StartAsync(); | |||||
var willMessage = new MqttApplicationMessage("My/last/will", new byte[0], MqttQualityOfServiceLevel.AtMostOnce, false); | var willMessage = new MqttApplicationMessage("My/last/will", new byte[0], MqttQualityOfServiceLevel.AtMostOnce, false); | ||||
var c1 = ConnectTestClient("c1", null, s); | var c1 = ConnectTestClient("c1", null, s); | ||||
@@ -63,7 +63,7 @@ namespace MQTTnet.Core.Tests | |||||
await Task.Delay(1000); | await Task.Delay(1000); | ||||
s.Stop(); | |||||
s.StopAsync(); | |||||
Assert.AreEqual(1, receivedMessagesCount); | Assert.AreEqual(1, receivedMessagesCount); | ||||
} | } | ||||
@@ -72,7 +72,7 @@ namespace MQTTnet.Core.Tests | |||||
public async Task MqttServer_Unsubscribe() | public async Task MqttServer_Unsubscribe() | ||||
{ | { | ||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); | var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); | ||||
s.Start(); | |||||
s.StartAsync(); | |||||
var c1 = ConnectTestClient("c1", null, s); | var c1 = ConnectTestClient("c1", null, s); | ||||
var c2 = ConnectTestClient("c2", null, s); | var c2 = ConnectTestClient("c2", null, s); | ||||
@@ -97,7 +97,7 @@ namespace MQTTnet.Core.Tests | |||||
await Task.Delay(500); | await Task.Delay(500); | ||||
Assert.AreEqual(1, receivedMessagesCount); | Assert.AreEqual(1, receivedMessagesCount); | ||||
s.Stop(); | |||||
s.StopAsync(); | |||||
await Task.Delay(500); | await Task.Delay(500); | ||||
Assert.AreEqual(1, receivedMessagesCount); | Assert.AreEqual(1, receivedMessagesCount); | ||||
@@ -107,7 +107,7 @@ namespace MQTTnet.Core.Tests | |||||
public async Task MqttServer_Publish() | public async Task MqttServer_Publish() | ||||
{ | { | ||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); | var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); | ||||
s.Start(); | |||||
s.StartAsync(); | |||||
var c1 = ConnectTestClient("c1", null, s); | var c1 = ConnectTestClient("c1", null, s); | ||||
@@ -120,7 +120,7 @@ namespace MQTTnet.Core.Tests | |||||
s.Publish(message); | s.Publish(message); | ||||
await Task.Delay(500); | await Task.Delay(500); | ||||
s.Stop(); | |||||
s.StopAsync(); | |||||
Assert.AreEqual(1, receivedMessagesCount); | Assert.AreEqual(1, receivedMessagesCount); | ||||
} | } | ||||
@@ -146,7 +146,7 @@ namespace MQTTnet.Core.Tests | |||||
int expectedReceivedMessagesCount) | int expectedReceivedMessagesCount) | ||||
{ | { | ||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); | var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); | ||||
s.Start(); | |||||
s.StartAsync(); | |||||
var c1 = ConnectTestClient("c1", null, s); | var c1 = ConnectTestClient("c1", null, s); | ||||
var c2 = ConnectTestClient("c2", null, s); | var c2 = ConnectTestClient("c2", null, s); | ||||
@@ -162,7 +162,7 @@ namespace MQTTnet.Core.Tests | |||||
await Task.Delay(500); | await Task.Delay(500); | ||||
s.Stop(); | |||||
s.StopAsync(); | |||||
Assert.AreEqual(expectedReceivedMessagesCount, receivedMessagesCount); | Assert.AreEqual(expectedReceivedMessagesCount, receivedMessagesCount); | ||||
} | } | ||||
@@ -1,4 +1,5 @@ | |||||
using System; | using System; | ||||
using System.Threading.Tasks; | |||||
using MQTTnet.Core.Adapter; | using MQTTnet.Core.Adapter; | ||||
using MQTTnet.Core.Server; | using MQTTnet.Core.Server; | ||||
@@ -13,12 +14,14 @@ namespace MQTTnet.Core.Tests | |||||
ClientConnected?.Invoke(this, eventArgs); | ClientConnected?.Invoke(this, eventArgs); | ||||
} | } | ||||
public void Start(MqttServerOptions options) | |||||
public Task StartAsync(MqttServerOptions options) | |||||
{ | { | ||||
return Task.FromResult(0); | |||||
} | } | ||||
public void Stop() | |||||
{ | |||||
public Task StopAsync() | |||||
{ | |||||
return Task.FromResult(0); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -153,12 +153,12 @@ namespace MQTTnet.TestApp.NetCore | |||||
}; | }; | ||||
var mqttServer = new MqttServerFactory().CreateMqttServer(options); | var mqttServer = new MqttServerFactory().CreateMqttServer(options); | ||||
mqttServer.Start(); | |||||
mqttServer.StartAsync(); | |||||
Console.WriteLine("Press any key to exit."); | Console.WriteLine("Press any key to exit."); | ||||
Console.ReadLine(); | Console.ReadLine(); | ||||
mqttServer.Stop(); | |||||
mqttServer.StopAsync(); | |||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
{ | { | ||||
@@ -167,7 +167,7 @@ namespace MQTTnet.TestApp.NetFramework | |||||
}); | }); | ||||
} | } | ||||
private static void RunServerAsync() | |||||
private static async Task RunServerAsync() | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
@@ -201,12 +201,12 @@ namespace MQTTnet.TestApp.NetFramework | |||||
stopwatch.Restart(); | stopwatch.Restart(); | ||||
} | } | ||||
}; | }; | ||||
mqttServer.Start(); | |||||
await mqttServer.StartAsync(); | |||||
Console.WriteLine("Press any key to exit."); | Console.WriteLine("Press any key to exit."); | ||||
Console.ReadLine(); | Console.ReadLine(); | ||||
mqttServer.Stop(); | |||||
await mqttServer.StopAsync(); | |||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
{ | { | ||||
@@ -121,7 +121,7 @@ namespace MQTTnet.TestApp.NetFramework | |||||
} | } | ||||
} | } | ||||
private static void RunServerAsync(string[] arguments) | |||||
private static async Task RunServerAsync(string[] arguments) | |||||
{ | { | ||||
MqttTrace.TraceMessagePublished += (s, e) => | MqttTrace.TraceMessagePublished += (s, e) => | ||||
{ | { | ||||
@@ -151,12 +151,12 @@ namespace MQTTnet.TestApp.NetFramework | |||||
}; | }; | ||||
var mqttServer = new MqttServerFactory().CreateMqttServer(options); | var mqttServer = new MqttServerFactory().CreateMqttServer(options); | ||||
mqttServer.Start(); | |||||
await mqttServer.StartAsync(); | |||||
Console.WriteLine("Press any key to exit."); | Console.WriteLine("Press any key to exit."); | ||||
Console.ReadLine(); | Console.ReadLine(); | ||||
mqttServer.Stop(); | |||||
await mqttServer.StopAsync(); | |||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
{ | { | ||||
@@ -166,4 +166,4 @@ namespace MQTTnet.TestApp.NetFramework | |||||
Console.ReadLine(); | Console.ReadLine(); | ||||
} | } | ||||
} | } | ||||
} | |||||
} |