@@ -19,6 +19,7 @@ | |||
* [Client] The disconnected event now contains the exception which was thrown and causing the disconnect. | |||
* [Server] Fixed an issue which lets the server block 1 second after accepting a new connection (thanks to @kpreisser). | |||
* [Server] The server now allows managing client subscriptions. | |||
* [Server] Added events for topic subscriptions. | |||
</releaseNotes> | |||
<copyright>Copyright Christian Kratky 2016-2018</copyright> | |||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | |||
@@ -12,5 +12,7 @@ namespace MQTTnet.Server | |||
public TimeSpan LastPacketReceived { get; set; } | |||
public TimeSpan LastNonKeepAlivePacketReceived { get; set; } | |||
public int PendingApplicationMessages { get; set; } | |||
} | |||
} |
@@ -6,10 +6,13 @@ namespace MQTTnet.Server | |||
{ | |||
public interface IMqttServer : IApplicationMessageReceiver, IApplicationMessagePublisher | |||
{ | |||
event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | |||
event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected; | |||
event EventHandler<MqttServerStartedEventArgs> Started; | |||
event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | |||
event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected; | |||
event EventHandler<MqttClientSubscribedTopicEventArgs> ClientSubscribedTopic; | |||
event EventHandler<MqttClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopic; | |||
Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync(); | |||
Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters); | |||
@@ -15,16 +15,18 @@ namespace MQTTnet.Server | |||
private readonly ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>(); | |||
private readonly SemaphoreSlim _queueWaitSemaphore = new SemaphoreSlim(0); | |||
private readonly IMqttServerOptions _options; | |||
private readonly MqttClientSession _session; | |||
private readonly MqttClientSession _clientSession; | |||
private readonly IMqttNetLogger _logger; | |||
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession session, IMqttNetLogger logger) | |||
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetLogger logger) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_session = session ?? throw new ArgumentNullException(nameof(session)); | |||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public int Count => _queue.Count; | |||
public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken) | |||
{ | |||
if (adapter == null) throw new ArgumentNullException(nameof(adapter)); | |||
@@ -44,7 +46,7 @@ namespace MQTTnet.Server | |||
_queue.Enqueue(packet); | |||
_queueWaitSemaphore.Release(); | |||
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _session.ClientId); | |||
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _clientSession.ClientId); | |||
} | |||
private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) | |||
@@ -61,7 +63,7 @@ namespace MQTTnet.Server | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.Error<MqttClientPendingMessagesQueue>(exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _session.ClientId); | |||
_logger.Error<MqttClientPendingMessagesQueue>(exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _clientSession.ClientId); | |||
} | |||
} | |||
@@ -78,24 +80,24 @@ namespace MQTTnet.Server | |||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packet).ConfigureAwait(false); | |||
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _session.ClientId); | |||
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); | |||
} | |||
catch (Exception exception) | |||
{ | |||
if (exception is MqttCommunicationTimedOutException) | |||
{ | |||
_logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to timeout (ClientId: {0}).", _session.ClientId); | |||
_logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to timeout (ClientId: {0}).", _clientSession.ClientId); | |||
} | |||
else if (exception is MqttCommunicationException) | |||
{ | |||
_logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to communication exception (ClientId: {0}).", _session.ClientId); | |||
_logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to communication exception (ClientId: {0}).", _clientSession.ClientId); | |||
} | |||
else if (exception is OperationCanceledException) | |||
{ | |||
} | |||
else | |||
{ | |||
_logger.Error<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed (ClientId: {0}).", _session.ClientId); | |||
_logger.Error<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId); | |||
} | |||
if (packet is MqttPublishPacket publishPacket) | |||
@@ -108,7 +110,7 @@ namespace MQTTnet.Server | |||
} | |||
} | |||
await _session.StopAsync(); | |||
await _clientSession.StopAsync(); | |||
} | |||
} | |||
@@ -21,10 +21,7 @@ namespace MQTTnet.Server | |||
private readonly IMqttServerOptions _options; | |||
private readonly IMqttNetLogger _logger; | |||
private readonly MqttClientSessionsManager _sessionsManager; | |||
private readonly MqttRetainedMessagesManager _retainedMessagesManager; | |||
private readonly MqttClientSubscriptionsManager _subscriptionsManager; | |||
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue; | |||
private IMqttChannelAdapter _adapter; | |||
private CancellationTokenSource _cancellationTokenSource; | |||
@@ -34,20 +31,24 @@ namespace MQTTnet.Server | |||
string clientId, | |||
IMqttServerOptions options, | |||
MqttRetainedMessagesManager retainedMessagesManager, | |||
MqttClientSessionsManager sessionsManager, | |||
IMqttNetLogger logger) | |||
{ | |||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); | |||
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
ClientId = clientId; | |||
_subscriptionsManager = new MqttClientSubscriptionsManager(_options, clientId); | |||
_pendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); | |||
SubscriptionsManager = new MqttClientSubscriptionsManager(_options, clientId); | |||
PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); | |||
} | |||
public Func<MqttClientSession, MqttApplicationMessage, Task> ApplicationMessageReceivedCallback { get; set; } | |||
public MqttClientSubscriptionsManager SubscriptionsManager { get; } | |||
public MqttClientPendingMessagesQueue PendingMessagesQueue { get; } | |||
public string ClientId { get; } | |||
public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion; | |||
@@ -71,7 +72,7 @@ namespace MQTTnet.Server | |||
_adapter = adapter; | |||
_cancellationTokenSource = cancellationTokenSource; | |||
_pendingMessagesQueue.Start(adapter, cancellationTokenSource.Token); | |||
PendingMessagesQueue.Start(adapter, cancellationTokenSource.Token); | |||
_lastPacketReceivedTracker.Restart(); | |||
_lastNonKeepAlivePacketReceivedTracker.Restart(); | |||
@@ -123,7 +124,7 @@ namespace MQTTnet.Server | |||
if (willMessage != null) | |||
{ | |||
_willMessage = null; //clear willmessage so it is send just once | |||
await _sessionsManager.DispatchApplicationMessageAsync(this, willMessage).ConfigureAwait(false); | |||
await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false); | |||
} | |||
} | |||
} | |||
@@ -132,7 +133,7 @@ namespace MQTTnet.Server | |||
{ | |||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | |||
var result = await _subscriptionsManager.CheckSubscriptionsAsync(applicationMessage); | |||
var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage); | |||
if (!result.IsSubscribed) | |||
{ | |||
return; | |||
@@ -141,30 +142,40 @@ namespace MQTTnet.Server | |||
var publishPacket = applicationMessage.ToPublishPacket(); | |||
publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel; | |||
_pendingMessagesQueue.Enqueue(publishPacket); | |||
PendingMessagesQueue.Enqueue(publishPacket); | |||
} | |||
public Task SubscribeAsync(IList<TopicFilter> topicFilters) | |||
{ | |||
return _subscriptionsManager.SubscribeAsync(new MqttSubscribePacket | |||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); | |||
var response = SubscriptionsManager.SubscribeAsync(new MqttSubscribePacket | |||
{ | |||
TopicFilters = topicFilters | |||
}); | |||
return response; | |||
} | |||
public Task UnsubscribeAsync(IList<string> topicFilters) | |||
{ | |||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); | |||
return _subscriptionsManager.UnsubscribeAsync(new MqttUnsubscribePacket | |||
var response = SubscriptionsManager.UnsubscribeAsync(new MqttUnsubscribePacket | |||
{ | |||
TopicFilters = topicFilters | |||
}); | |||
return response; | |||
} | |||
public void Dispose() | |||
{ | |||
_pendingMessagesQueue?.Dispose(); | |||
ApplicationMessageReceivedCallback = null; | |||
SubscriptionsManager?.Dispose(); | |||
PendingMessagesQueue?.Dispose(); | |||
_cancellationTokenSource?.Dispose(); | |||
} | |||
@@ -250,7 +261,7 @@ namespace MQTTnet.Server | |||
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) | |||
{ | |||
var subscribeResult = await _subscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false); | |||
var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false); | |||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, subscribeResult.ResponsePacket).ConfigureAwait(false); | |||
if (subscribeResult.CloseConnection) | |||
@@ -264,7 +275,7 @@ namespace MQTTnet.Server | |||
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) | |||
{ | |||
var unsubscribeResult = await _subscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false); | |||
var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false); | |||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, unsubscribeResult); | |||
} | |||
@@ -285,7 +296,7 @@ namespace MQTTnet.Server | |||
{ | |||
case MqttQualityOfServiceLevel.AtMostOnce: | |||
{ | |||
return _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage); | |||
return ApplicationMessageReceivedCallback?.Invoke(this, applicationMessage); | |||
} | |||
case MqttQualityOfServiceLevel.AtLeastOnce: | |||
{ | |||
@@ -304,7 +315,7 @@ namespace MQTTnet.Server | |||
private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) | |||
{ | |||
await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage).ConfigureAwait(false); | |||
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false); | |||
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }; | |||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response).ConfigureAwait(false); | |||
@@ -313,7 +324,7 @@ namespace MQTTnet.Server | |||
private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) | |||
{ | |||
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] | |||
await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage).ConfigureAwait(false); | |||
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false); | |||
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; | |||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response).ConfigureAwait(false); | |||
@@ -12,27 +12,32 @@ using MQTTnet.Serializer; | |||
namespace MQTTnet.Server | |||
{ | |||
public sealed class MqttClientSessionsManager | |||
public sealed class MqttClientSessionsManager : IDisposable | |||
{ | |||
private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>(); | |||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | |||
private readonly IMqttServerOptions _options; | |||
private readonly MqttServer _server; | |||
private readonly MqttRetainedMessagesManager _retainedMessagesManager; | |||
private readonly IMqttNetLogger _logger; | |||
public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, MqttServer server, IMqttNetLogger logger) | |||
public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger) | |||
{ | |||
_server = server ?? throw new ArgumentNullException(nameof(server)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); | |||
} | |||
public async Task RunClientSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) | |||
public Action<ConnectedMqttClient> ClientConnectedCallback { get; set; } | |||
public Action<ConnectedMqttClient> ClientDisconnectedCallback { get; set; } | |||
public Action<string, TopicFilter> ClientSubscribedTopicCallback { get; set; } | |||
public Action<string, string> ClientUnsubscribedTopicCallback { get; set; } | |||
public Action<string, MqttApplicationMessage> ApplicationMessageReceivedCallback { get; set; } | |||
public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) | |||
{ | |||
var clientId = string.Empty; | |||
MqttClientSession clientSession = null; | |||
try | |||
{ | |||
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false) is MqttConnectPacket connectPacket)) | |||
@@ -56,21 +61,22 @@ namespace MQTTnet.Server | |||
return; | |||
} | |||
var clientSession = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false); | |||
var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false); | |||
clientSession = result.Session; | |||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket | |||
{ | |||
ConnectReturnCode = connectReturnCode, | |||
IsSessionPresent = clientSession.IsExistingSession | |||
IsSessionPresent = result.IsExistingSession | |||
}).ConfigureAwait(false); | |||
_server.OnClientConnected(new ConnectedMqttClient | |||
ClientConnectedCallback?.Invoke(new ConnectedMqttClient | |||
{ | |||
ClientId = clientId, | |||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion | |||
}); | |||
await clientSession.Session.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); | |||
await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); | |||
} | |||
catch (Exception exception) | |||
{ | |||
@@ -87,10 +93,11 @@ namespace MQTTnet.Server | |||
// ignored | |||
} | |||
_server.OnClientDisconnected(new ConnectedMqttClient | |||
ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient | |||
{ | |||
ClientId = clientId, | |||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion | |||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion, | |||
PendingApplicationMessages = clientSession?.PendingMessagesQueue.Count ?? 0 | |||
}); | |||
} | |||
} | |||
@@ -123,7 +130,8 @@ namespace MQTTnet.Server | |||
ClientId = s.Value.ClientId, | |||
ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311, | |||
LastPacketReceived = s.Value.LastPacketReceived, | |||
LastNonKeepAlivePacketReceived = s.Value.LastNonKeepAlivePacketReceived | |||
LastNonKeepAlivePacketReceived = s.Value.LastNonKeepAlivePacketReceived, | |||
PendingApplicationMessages = s.Value.PendingMessagesQueue.Count | |||
}).ToList(); | |||
} | |||
finally | |||
@@ -147,7 +155,7 @@ namespace MQTTnet.Server | |||
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false); | |||
} | |||
_server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage); | |||
ApplicationMessageReceivedCallback?.Invoke(senderClientSession?.ClientId, applicationMessage); | |||
} | |||
catch (Exception exception) | |||
{ | |||
@@ -271,7 +279,14 @@ namespace MQTTnet.Server | |||
{ | |||
isExistingSession = false; | |||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, _retainedMessagesManager, this, _logger); | |||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, _retainedMessagesManager, _logger) | |||
{ | |||
ApplicationMessageReceivedCallback = DispatchApplicationMessageAsync | |||
}; | |||
clientSession.SubscriptionsManager.TopicSubscribedCallback = ClientSubscribedTopicCallback; | |||
clientSession.SubscriptionsManager.TopicUnsubscribedCallback = ClientUnsubscribedTopicCallback; | |||
_sessions[connectPacket.ClientId] = clientSession; | |||
_logger.Trace<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId); | |||
@@ -284,5 +299,16 @@ namespace MQTTnet.Server | |||
_semaphore.Release(); | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
ClientConnectedCallback = null; | |||
ClientDisconnectedCallback = null; | |||
ClientSubscribedTopicCallback = null; | |||
ClientUnsubscribedTopicCallback = null; | |||
ApplicationMessageReceivedCallback = null; | |||
_semaphore?.Dispose(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
using System; | |||
namespace MQTTnet.Server | |||
{ | |||
public class MqttClientSubscribedTopicEventArgs : EventArgs | |||
{ | |||
public MqttClientSubscribedTopicEventArgs(string clientId, TopicFilter topicFilter) | |||
{ | |||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); | |||
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); | |||
} | |||
public string ClientId { get; } | |||
public TopicFilter TopicFilter { get; } | |||
} | |||
} |
@@ -8,19 +8,22 @@ using MQTTnet.Protocol; | |||
namespace MQTTnet.Server | |||
{ | |||
public sealed class MqttClientSubscriptionsManager | |||
public sealed class MqttClientSubscriptionsManager : IDisposable | |||
{ | |||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | |||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>(); | |||
private readonly IMqttServerOptions _options; | |||
private readonly string _clientId; | |||
public MqttClientSubscriptionsManager(IMqttServerOptions options, string clientId) | |||
{ | |||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||
_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); | |||
} | |||
public Action<string, TopicFilter> TopicSubscribedCallback { get; set; } | |||
public Action<string, string> TopicUnsubscribedCallback { get; set; } | |||
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttSubscribePacket subscribePacket) | |||
{ | |||
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); | |||
@@ -54,6 +57,7 @@ namespace MQTTnet.Server | |||
if (interceptorContext.AcceptSubscription) | |||
{ | |||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; | |||
TopicSubscribedCallback?.Invoke(_clientId, topicFilter); | |||
} | |||
} | |||
} | |||
@@ -75,6 +79,7 @@ namespace MQTTnet.Server | |||
foreach (var topicFilter in unsubscribePacket.TopicFilters) | |||
{ | |||
_subscriptions.Remove(topicFilter); | |||
TopicUnsubscribedCallback?.Invoke(_clientId, topicFilter); | |||
} | |||
} | |||
finally | |||
@@ -119,6 +124,22 @@ namespace MQTTnet.Server | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
_semaphore?.Dispose(); | |||
} | |||
private static MqttSubscribeReturnCode ConvertToMaximumQoS(MqttQualityOfServiceLevel qualityOfServiceLevel) | |||
{ | |||
switch (qualityOfServiceLevel) | |||
{ | |||
case MqttQualityOfServiceLevel.AtMostOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS0; | |||
case MqttQualityOfServiceLevel.AtLeastOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS1; | |||
case MqttQualityOfServiceLevel.ExactlyOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS2; | |||
default: return MqttSubscribeReturnCode.Failure; | |||
} | |||
} | |||
private MqttSubscriptionInterceptorContext InterceptSubscribe(TopicFilter topicFilter) | |||
{ | |||
var interceptorContext = new MqttSubscriptionInterceptorContext(_clientId, topicFilter); | |||
@@ -148,16 +169,5 @@ namespace MQTTnet.Server | |||
QualityOfServiceLevel = effectiveQoS | |||
}; | |||
} | |||
private static MqttSubscribeReturnCode ConvertToMaximumQoS(MqttQualityOfServiceLevel qualityOfServiceLevel) | |||
{ | |||
switch (qualityOfServiceLevel) | |||
{ | |||
case MqttQualityOfServiceLevel.AtMostOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS0; | |||
case MqttQualityOfServiceLevel.AtLeastOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS1; | |||
case MqttQualityOfServiceLevel.ExactlyOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS2; | |||
default: return MqttSubscribeReturnCode.Failure; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
using System; | |||
namespace MQTTnet.Server | |||
{ | |||
public class MqttClientUnsubscribedTopicEventArgs : EventArgs | |||
{ | |||
public MqttClientUnsubscribedTopicEventArgs(string clientId, string topicFilter) | |||
{ | |||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); | |||
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); | |||
} | |||
public string ClientId { get; } | |||
public string TopicFilter { get; } | |||
} | |||
} |
@@ -8,7 +8,7 @@ using MQTTnet.Packets; | |||
namespace MQTTnet.Server | |||
{ | |||
public sealed class MqttRetainedMessagesManager | |||
public sealed class MqttRetainedMessagesManager : IDisposable | |||
{ | |||
private readonly Dictionary<string, MqttApplicationMessage> _retainedMessages = new Dictionary<string, MqttApplicationMessage>(); | |||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | |||
@@ -102,6 +102,11 @@ namespace MQTTnet.Server | |||
return retainedMessages; | |||
} | |||
public void Dispose() | |||
{ | |||
_semaphore?.Dispose(); | |||
} | |||
private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage) | |||
{ | |||
var saveIsRequired = false; | |||
@@ -27,8 +27,12 @@ namespace MQTTnet.Server | |||
} | |||
public event EventHandler<MqttServerStartedEventArgs> Started; | |||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; | |||
public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected; | |||
public event EventHandler<MqttClientSubscribedTopicEventArgs> ClientSubscribedTopic; | |||
public event EventHandler<MqttClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopic; | |||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; | |||
public Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync() | |||
@@ -71,11 +75,19 @@ namespace MQTTnet.Server | |||
if (_cancellationTokenSource != null) throw new InvalidOperationException("The server is already started."); | |||
_cancellationTokenSource = new CancellationTokenSource(); | |||
_retainedMessagesManager = new MqttRetainedMessagesManager(_options, _logger); | |||
_clientSessionsManager = new MqttClientSessionsManager(_options, _retainedMessagesManager, this, _logger); | |||
_retainedMessagesManager = new MqttRetainedMessagesManager(_options, _logger); | |||
await _retainedMessagesManager.LoadMessagesAsync(); | |||
_clientSessionsManager = new MqttClientSessionsManager(_options, _retainedMessagesManager, _logger) | |||
{ | |||
ClientConnectedCallback = OnClientConnected, | |||
ClientDisconnectedCallback = OnClientDisconnected, | |||
ClientSubscribedTopicCallback = OnClientSubscribedTopic, | |||
ClientUnsubscribedTopicCallback = OnClientUnsubscribedTopic, | |||
ApplicationMessageReceivedCallback = OnApplicationMessageReceived | |||
}; | |||
foreach (var adapter in _adapters) | |||
{ | |||
adapter.ClientAccepted += OnClientAccepted; | |||
@@ -110,39 +122,46 @@ namespace MQTTnet.Server | |||
} | |||
finally | |||
{ | |||
_clientSessionsManager?.Dispose(); | |||
_retainedMessagesManager?.Dispose(); | |||
_cancellationTokenSource = null; | |||
_retainedMessagesManager = null; | |||
_clientSessionsManager = null; | |||
} | |||
} | |||
internal void OnClientConnected(ConnectedMqttClient client) | |||
private void OnClientConnected(ConnectedMqttClient client) | |||
{ | |||
if (client == null) throw new ArgumentNullException(nameof(client)); | |||
_logger.Info<MqttServer>("Client '{0}': Connected.", client.ClientId); | |||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client)); | |||
} | |||
internal void OnClientDisconnected(ConnectedMqttClient client) | |||
private void OnClientDisconnected(ConnectedMqttClient client) | |||
{ | |||
if (client == null) throw new ArgumentNullException(nameof(client)); | |||
_logger.Info<MqttServer>("Client '{0}': Disconnected.", client.ClientId); | |||
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client)); | |||
} | |||
internal void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage) | |||
private void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter) | |||
{ | |||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | |||
ClientSubscribedTopic?.Invoke(this, new MqttClientSubscribedTopicEventArgs(clientId, topicFilter)); | |||
} | |||
private void OnClientUnsubscribedTopic(string clientId, string topicFilter) | |||
{ | |||
ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter)); | |||
} | |||
private void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage) | |||
{ | |||
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage)); | |||
} | |||
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) | |||
{ | |||
eventArgs.SessionTask = Task.Run( | |||
async () => await _clientSessionsManager.RunClientSessionAsync(eventArgs.Client, _cancellationTokenSource.Token).ConfigureAwait(false), | |||
async () => await _clientSessionsManager.RunSessionAsync(eventArgs.Client, _cancellationTokenSource.Token).ConfigureAwait(false), | |||
_cancellationTokenSource.Token); | |||
} | |||
} | |||
@@ -79,7 +79,7 @@ namespace MQTTnet.Core.Tests | |||
} | |||
[TestMethod] | |||
public async Task MqttServer_Unsubscribe() | |||
public async Task MqttServer_SubscribeUnsubscribe() | |||
{ | |||
var serverAdapter = new TestMqttServerAdapter(); | |||
var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); | |||
@@ -99,13 +99,31 @@ namespace MQTTnet.Core.Tests | |||
await c2.PublishAsync(message); | |||
Assert.AreEqual(0, receivedMessagesCount); | |||
var subscribeEventCalled = false; | |||
s.ClientSubscribedTopic += (_, e) => | |||
{ | |||
subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == "c1"; | |||
}; | |||
await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce)); | |||
await Task.Delay(100); | |||
Assert.IsTrue(subscribeEventCalled, "Subscribe event not called."); | |||
await c2.PublishAsync(message); | |||
await Task.Delay(500); | |||
Assert.AreEqual(1, receivedMessagesCount); | |||
var unsubscribeEventCalled = false; | |||
s.ClientUnsubscribedTopic += (_, e) => | |||
{ | |||
unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == "c1"; | |||
}; | |||
await c1.UnsubscribeAsync("a"); | |||
await Task.Delay(100); | |||
Assert.IsTrue(unsubscribeEventCalled, "Unsubscribe event not called."); | |||
await c2.PublishAsync(message); | |||
await Task.Delay(500); | |||
@@ -343,7 +361,7 @@ namespace MQTTnet.Core.Tests | |||
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); | |||
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); | |||
c1.ApplicationMessageReceived += (_, e) => | |||
{ | |||
if (Encoding.UTF8.GetString(e.ApplicationMessage.Payload) == "The body") | |||