Files
MQTTnet/MQTTnet.Core/Server/MqttClientSession.cs
T
Christian Kratky 105abeba06 * [Server] Added support for complex client IDs.
* [Server] Fixed an issue with not correctly removed old client sessions.
* [Server] Several minor performance improvements.
* [Server] An existing client session is no longer closed if a new client connection is invalid.
* [Client] Added support for sending "CleanSession" flag.
2017-05-05 21:34:37 +02:00

184 lines
7.4 KiB
C#

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.Internal;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Server
{
public sealed class MqttClientSession : IDisposable
{
private readonly ConcurrentDictionary<ushort, MqttPublishPacket> _pendingIncomingPublications = new ConcurrentDictionary<ushort, MqttPublishPacket>();
private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager();
private readonly MqttClientMessageQueue _messageQueue;
private readonly Action<MqttClientSession, MqttPublishPacket> _publishPacketReceivedCallback;
private readonly MqttServerOptions _options;
private CancellationTokenSource _cancellationTokenSource;
private IMqttCommunicationAdapter _adapter;
private string _identifier;
private MqttApplicationMessage _willApplicationMessage;
public MqttClientSession(MqttServerOptions options, Action<MqttClientSession, MqttPublishPacket> publishPacketReceivedCallback)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_publishPacketReceivedCallback = publishPacketReceivedCallback ?? throw new ArgumentNullException(nameof(publishPacketReceivedCallback));
_messageQueue = new MqttClientMessageQueue(options);
}
public bool IsConnected => _adapter != null;
public async Task RunAsync(string identifier, MqttApplicationMessage willApplicationMessage, IMqttCommunicationAdapter adapter)
{
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
_willApplicationMessage = willApplicationMessage;
try
{
_identifier = identifier;
_adapter = adapter;
_cancellationTokenSource = new CancellationTokenSource();
_messageQueue.Start(adapter);
while (!_cancellationTokenSource.IsCancellationRequested)
{
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero);
await HandleIncomingPacketAsync(packet);
}
}
catch (MqttCommunicationException)
{
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttClientSession), exception, $"Client '{_identifier}': Unhandled exception while processing client packets.");
}
finally
{
if (willApplicationMessage != null)
{
_publishPacketReceivedCallback(this, _willApplicationMessage.ToPublishPacket());
}
_messageQueue.Stop();
_cancellationTokenSource.Cancel();
_adapter = null;
MqttTrace.Information(nameof(MqttClientSession), $"Client '{_identifier}': Disconnected.");
}
}
public void EnqueuePublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
{
if (senderClientSession == null) throw new ArgumentNullException(nameof(senderClientSession));
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
if (!_subscriptionsManager.IsTopicSubscribed(publishPacket))
{
return;
}
_messageQueue.Enqueue(senderClientSession, publishPacket);
MqttTrace.Verbose(nameof(MqttClientSession), $"Client '{_identifier}: Enqueued pending publish packet.");
}
public void Dispose()
{
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
}
private Task HandleIncomingPacketAsync(MqttBasePacket packet)
{
var subscribePacket = packet as MqttSubscribePacket;
if (subscribePacket != null)
{
return _adapter.SendPacketAsync(_subscriptionsManager.Subscribe(subscribePacket), _options.DefaultCommunicationTimeout);
}
var unsubscribePacket = packet as MqttUnsubscribePacket;
if (unsubscribePacket != null)
{
return _adapter.SendPacketAsync(_subscriptionsManager.Unsubscribe(unsubscribePacket), _options.DefaultCommunicationTimeout);
}
var publishPacket = packet as MqttPublishPacket;
if (publishPacket != null)
{
return HandleIncomingPublishPacketAsync(publishPacket);
}
var pubRelPacket = packet as MqttPubRelPacket;
if (pubRelPacket != null)
{
return HandleIncomingPubRelPacketAsync(pubRelPacket);
}
var pubAckPacket = packet as MqttPubAckPacket;
if (pubAckPacket != null)
{
return HandleIncomingPubAckPacketAsync(pubAckPacket);
}
if (packet is MqttPingReqPacket)
{
return _adapter.SendPacketAsync(new MqttPingRespPacket(), _options.DefaultCommunicationTimeout);
}
if (packet is MqttDisconnectPacket || packet is MqttConnectPacket)
{
_cancellationTokenSource.Cancel();
return Task.FromResult((object)null);
}
MqttTrace.Warning(nameof(MqttClientSession), $"Client '{_identifier}': Received not supported packet ({packet}). Closing connection.");
_cancellationTokenSource.Cancel();
return Task.FromResult((object)null);
}
private async Task HandleIncomingPubAckPacketAsync(MqttPubAckPacket pubAckPacket)
{
await Task.FromResult((object)null);
}
private async Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket)
{
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
_publishPacketReceivedCallback(this, publishPacket);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
await _adapter.SendPacketAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
_publishPacketReceivedCallback(this, publishPacket);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
_pendingIncomingPublications[publishPacket.PacketIdentifier] = publishPacket;
await _adapter.SendPacketAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
}
}
private async Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket)
{
MqttPublishPacket publishPacket;
if (!_pendingIncomingPublications.TryRemove(pubRelPacket.PacketIdentifier, out publishPacket))
{
return;
}
await _adapter.SendPacketAsync(new MqttPubCompPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
_publishPacketReceivedCallback(this, publishPacket);
}
}
}