mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-18 18:43:10 +08:00
Merge branch 'develop' into master
This commit is contained in:
@@ -17,12 +17,12 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
string subprotocol = null;
|
||||
|
||||
if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues)
|
||||
&& requestedSubProtocolValues.Count > 0
|
||||
&& requestedSubProtocolValues.Any(v => v.ToLower() == "mqtt")
|
||||
)
|
||||
if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
|
||||
{
|
||||
subprotocol = "mqtt";
|
||||
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
|
||||
subprotocol = requestedSubProtocolValues
|
||||
.OrderByDescending(p => p.Length)
|
||||
.FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
|
||||
}
|
||||
|
||||
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
|
||||
|
||||
@@ -3,12 +3,13 @@ using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Serializer;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable
|
||||
public class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
@@ -26,8 +27,7 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
|
||||
|
||||
var channel = new MqttWebSocketServerChannel(webSocket);
|
||||
var clientAdapter = new MqttChannelAdapter(channel, new MqttPacketSerializer(), new MqttNetLogger());
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket), new MqttPacketSerializer(), new MqttNetLogger());
|
||||
|
||||
var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter);
|
||||
ClientAccepted?.Invoke(this, eventArgs);
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public class MqttWebSocketServerChannel : IMqttChannel, IDisposable
|
||||
{
|
||||
private WebSocket _webSocket;
|
||||
|
||||
public MqttWebSocketServerChannel(WebSocket webSocket)
|
||||
{
|
||||
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
|
||||
|
||||
SendStream = new WebSocketStream(_webSocket);
|
||||
ReceiveStream = SendStream;
|
||||
}
|
||||
|
||||
public Stream SendStream { get; private set; }
|
||||
public Stream ReceiveStream { get; private set; }
|
||||
|
||||
public Task ConnectAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SendStream?.Dispose();
|
||||
ReceiveStream?.Dispose();
|
||||
|
||||
_webSocket?.Dispose();
|
||||
|
||||
SendStream = null;
|
||||
ReceiveStream = null;
|
||||
_webSocket = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,11 @@ namespace MQTTnet.Adapter
|
||||
{
|
||||
IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
Task ConnectAsync(TimeSpan timeout);
|
||||
Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
|
||||
Task DisconnectAsync(TimeSpan timeout);
|
||||
Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
|
||||
Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets);
|
||||
Task SendPacketsAsync(TimeSpan timeout, IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken);
|
||||
|
||||
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Adapter
|
||||
{
|
||||
public interface IMqttServerAdapter
|
||||
public interface IMqttServerAdapter : IDisposable
|
||||
{
|
||||
event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
@@ -19,11 +18,11 @@ namespace MQTTnet.Adapter
|
||||
private const uint ErrorOperationAborted = 0x800703E3;
|
||||
private const int ReadBufferSize = 4096; // TODO: Move buffer size to config
|
||||
|
||||
private bool _isDisposed;
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly IMqttNetLogger _logger;
|
||||
private readonly IMqttChannel _channel;
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
@@ -33,75 +32,52 @@ namespace MQTTnet.Adapter
|
||||
|
||||
public IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
public Task ConnectAsync(TimeSpan timeout)
|
||||
public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_logger.Verbose<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout);
|
||||
|
||||
return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout));
|
||||
return ExecuteAndWrapExceptionAsync(() =>
|
||||
Internal.TaskExtensions.TimeoutAfter(ct => _channel.ConnectAsync(ct), timeout, cancellationToken));
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(TimeSpan timeout)
|
||||
public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_logger.Verbose<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);
|
||||
|
||||
return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout));
|
||||
return ExecuteAndWrapExceptionAsync(() =>
|
||||
Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, cancellationToken));
|
||||
}
|
||||
|
||||
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
|
||||
public async Task SendPacketsAsync(TimeSpan timeout, IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return ExecuteAndWrapExceptionAsync(async () =>
|
||||
foreach (var packet in packets)
|
||||
{
|
||||
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
if (packet == null)
|
||||
{
|
||||
foreach (var packet in packets)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.Verbose<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout);
|
||||
|
||||
var chunks = PacketSerializer.Serialize(packet);
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeout > TimeSpan.Zero)
|
||||
{
|
||||
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
continue;
|
||||
}
|
||||
|
||||
await SendPacketAsync(timeout, cancellationToken, packet).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private Task SendPacketAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket packet)
|
||||
{
|
||||
return ExecuteAndWrapExceptionAsync(() =>
|
||||
{
|
||||
_logger.Verbose<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout);
|
||||
|
||||
var packetData = PacketSerializer.Serialize(packet);
|
||||
|
||||
return Internal.TaskExtensions.TimeoutAfter(ct => _channel.WriteAsync(
|
||||
packetData.Array,
|
||||
packetData.Offset,
|
||||
packetData.Count,
|
||||
ct), timeout, cancellationToken);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,11 +93,11 @@ namespace MQTTnet.Adapter
|
||||
{
|
||||
if (timeout > TimeSpan.Zero)
|
||||
{
|
||||
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
|
||||
receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfter(ct => ReceiveAsync(_channel, ct), timeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false);
|
||||
receivedMqttPacket = await ReceiveAsync(_channel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
|
||||
@@ -146,22 +122,22 @@ namespace MQTTnet.Adapter
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static async Task<ReceivedMqttPacket> ReceiveAsync(Stream stream, CancellationToken cancellationToken)
|
||||
private static async Task<ReceivedMqttPacket> ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken)
|
||||
{
|
||||
var header = await MqttPacketReader.ReadHeaderAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
var header = await MqttPacketReader.ReadHeaderAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
if (header == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (header.BodyLength == 0)
|
||||
{
|
||||
return new ReceivedMqttPacket(header, new MemoryStream(new byte[0], false));
|
||||
}
|
||||
|
||||
var body = header.BodyLength <= ReadBufferSize ? new MemoryStream(header.BodyLength) : new MemoryStream();
|
||||
var body = new MemoryStream(header.BodyLength);
|
||||
|
||||
var buffer = new byte[ReadBufferSize];
|
||||
var buffer = new byte[Math.Min(ReadBufferSize, header.BodyLength)];
|
||||
while (body.Length < header.BodyLength)
|
||||
{
|
||||
var bytesLeft = header.BodyLength - (int)body.Length;
|
||||
@@ -170,7 +146,7 @@ namespace MQTTnet.Adapter
|
||||
bytesLeft = buffer.Length;
|
||||
}
|
||||
|
||||
var readBytesCount = await stream.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false);
|
||||
var readBytesCount = await channel.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Check if the client closed the connection before sending the full body.
|
||||
if (readBytesCount == 0)
|
||||
@@ -240,7 +216,7 @@ namespace MQTTnet.Adapter
|
||||
public void Dispose()
|
||||
{
|
||||
_isDisposed = true;
|
||||
_semaphore?.Dispose();
|
||||
|
||||
_channel?.Dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Channel
|
||||
{
|
||||
public interface IMqttChannel : IDisposable
|
||||
{
|
||||
Stream SendStream { get; }
|
||||
Stream ReceiveStream { get; }
|
||||
|
||||
Task ConnectAsync();
|
||||
Task ConnectAsync(CancellationToken cancellationToken);
|
||||
Task DisconnectAsync();
|
||||
|
||||
Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
|
||||
Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,16 @@ namespace MQTTnet.Client
|
||||
public interface IMqttClientOptions
|
||||
{
|
||||
string ClientId { get; }
|
||||
|
||||
IMqttClientCredentials Credentials { get; }
|
||||
bool CleanSession { get; }
|
||||
MqttApplicationMessage WillMessage { get; }
|
||||
IMqttClientCredentials Credentials { get; }
|
||||
MqttProtocolVersion ProtocolVersion { get; }
|
||||
IMqttClientChannelOptions ChannelOptions { get; }
|
||||
|
||||
TimeSpan CommunicationTimeout { get; }
|
||||
TimeSpan KeepAlivePeriod { get; }
|
||||
TimeSpan? KeepAliveSendInterval { get; }
|
||||
MqttReceivedApplicationMessageProcessingMode ReceivedApplicationMessageProcessingMode { get; }
|
||||
|
||||
MqttProtocolVersion ProtocolVersion { get; }
|
||||
|
||||
IMqttClientChannelOptions ChannelOptions { get; }
|
||||
MqttApplicationMessage WillMessage { get; }
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,12 @@ namespace MQTTnet.Client
|
||||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||
private readonly Stopwatch _sendTracker = new Stopwatch();
|
||||
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1);
|
||||
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
||||
|
||||
private readonly IMqttClientAdapterFactory _adapterFactory;
|
||||
private readonly MqttPacketDispatcher _packetDispatcher;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
private IMqttClientOptions _options;
|
||||
private bool _isReceivingPackets;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private Task _packetReceiverTask;
|
||||
private Task _keepAliveMessageSenderTask;
|
||||
@@ -33,8 +33,6 @@ namespace MQTTnet.Client
|
||||
{
|
||||
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_packetDispatcher = new MqttPacketDispatcher(logger);
|
||||
}
|
||||
|
||||
public event EventHandler<MqttClientConnectedEventArgs> Connected;
|
||||
@@ -52,27 +50,27 @@ namespace MQTTnet.Client
|
||||
|
||||
try
|
||||
{
|
||||
_options = options;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_options = options;
|
||||
_packetIdentifierProvider.Reset();
|
||||
_packetDispatcher.Reset();
|
||||
|
||||
_adapter = _adapterFactory.CreateClientAdapter(options, _logger);
|
||||
|
||||
_logger.Verbose<MqttClient>("Trying to connect with server.");
|
||||
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
|
||||
await _adapter.ConnectAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
_logger.Verbose<MqttClient>("Connection with server established.");
|
||||
|
||||
await StartReceivingPacketsAsync().ConfigureAwait(false);
|
||||
StartReceivingPackets(_cancellationTokenSource.Token);
|
||||
|
||||
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
|
||||
var connectResponse = await AuthenticateAsync(options.WillMessage, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
_logger.Verbose<MqttClient>("MQTT connection with server established.");
|
||||
|
||||
_sendTracker.Restart();
|
||||
|
||||
if (_options.KeepAlivePeriod != TimeSpan.Zero)
|
||||
{
|
||||
StartSendingKeepAliveMessages();
|
||||
StartSendingKeepAliveMessages(_cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
IsConnected = true;
|
||||
@@ -92,16 +90,11 @@ namespace MQTTnet.Client
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
if (IsConnected && !_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
|
||||
await SendAsync(new MqttDisconnectPacket(), _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -122,7 +115,7 @@ namespace MQTTnet.Client
|
||||
TopicFilters = topicFilters.ToList()
|
||||
};
|
||||
|
||||
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket).ConfigureAwait(false);
|
||||
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count)
|
||||
{
|
||||
@@ -144,7 +137,7 @@ namespace MQTTnet.Client
|
||||
TopicFilters = topicFilters.ToList()
|
||||
};
|
||||
|
||||
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket).ConfigureAwait(false);
|
||||
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||
@@ -161,7 +154,7 @@ namespace MQTTnet.Client
|
||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||
{
|
||||
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
|
||||
await SendAsync(qosGroup.Cast<MqttBasePacket>().ToArray()).ConfigureAwait(false);
|
||||
await SendAsync(qosGroup, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||
@@ -169,7 +162,7 @@ namespace MQTTnet.Client
|
||||
foreach (var publishPacket in qosGroup)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
|
||||
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket).ConfigureAwait(false);
|
||||
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -180,13 +173,13 @@ namespace MQTTnet.Client
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
|
||||
|
||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false);
|
||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
var pubRelPacket = new MqttPubRelPacket
|
||||
{
|
||||
PacketIdentifier = pubRecPacket.PacketIdentifier
|
||||
};
|
||||
|
||||
await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket).ConfigureAwait(false);
|
||||
await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -207,7 +200,7 @@ namespace MQTTnet.Client
|
||||
_adapter?.Dispose();
|
||||
}
|
||||
|
||||
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
|
||||
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken)
|
||||
{
|
||||
var connectPacket = new MqttConnectPacket
|
||||
{
|
||||
@@ -219,7 +212,7 @@ namespace MQTTnet.Client
|
||||
WillMessage = willApplicationMessage
|
||||
};
|
||||
|
||||
var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket).ConfigureAwait(false);
|
||||
var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
||||
{
|
||||
throw new MqttConnectingFailedException(response.ConnectReturnCode);
|
||||
@@ -264,21 +257,19 @@ namespace MQTTnet.Client
|
||||
|
||||
try
|
||||
{
|
||||
if (_packetReceiverTask != null && _packetReceiverTask != sender)
|
||||
{
|
||||
_packetReceiverTask.Wait();
|
||||
}
|
||||
await WaitForTaskAsync(_packetReceiverTask, sender).ConfigureAwait(false);
|
||||
await WaitForTaskAsync(_keepAliveMessageSenderTask, sender).ConfigureAwait(false);
|
||||
|
||||
if (_keepAliveMessageSenderTask != null && _keepAliveMessageSenderTask != sender)
|
||||
{
|
||||
_keepAliveMessageSenderTask.Wait();
|
||||
await _keepAliveMessageSenderTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (_adapter != null)
|
||||
{
|
||||
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
|
||||
await _adapter.DisconnectAsync(_options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
_logger.Verbose<MqttClient>("Disconnected from adapter.");
|
||||
}
|
||||
catch (Exception adapterException)
|
||||
@@ -297,121 +288,63 @@ namespace MQTTnet.Client
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessReceivedPacketAsync(MqttBasePacket packet)
|
||||
private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
{
|
||||
await ProcessReceivedPublishPacketAsync(publishPacket).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet is MqttPingReqPacket)
|
||||
{
|
||||
await SendAsync(new MqttPingRespPacket()).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet is MqttDisconnectPacket)
|
||||
{
|
||||
await DisconnectAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet is MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
await ProcessReceivedPubRelPacket(pubRelPacket).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_packetDispatcher.Dispatch(packet);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClient>(exception, "Unhandled exception while processing received packet.");
|
||||
}
|
||||
return SendAsync(new[] { packet }, cancellationToken);
|
||||
}
|
||||
|
||||
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
|
||||
private Task SendAsync(IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var applicationMessage = publishPacket.ToApplicationMessage();
|
||||
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(_options.ClientId, applicationMessage));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClient>(exception, "Unhandled exception while handling application message.");
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
{
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
|
||||
{
|
||||
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
|
||||
}
|
||||
|
||||
throw new MqttCommunicationException("Received a not supported QoS level.");
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
var response = new MqttPubCompPacket
|
||||
{
|
||||
PacketIdentifier = pubRelPacket.PacketIdentifier
|
||||
};
|
||||
|
||||
return SendAsync(response);
|
||||
}
|
||||
|
||||
private Task SendAsync(params MqttBasePacket[] packets)
|
||||
{
|
||||
_sendTracker.Restart();
|
||||
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packets);
|
||||
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, packets, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
|
||||
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket
|
||||
{
|
||||
ushort? identifier = null;
|
||||
if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier)
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
identifier = requestPacketWithIdentifier.PacketIdentifier;
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout);
|
||||
await SendAsync(requestPacket).ConfigureAwait(false);
|
||||
_sendTracker.Restart();
|
||||
|
||||
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
|
||||
ushort identifier = 0;
|
||||
if (requestPacket is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue)
|
||||
{
|
||||
identifier = packetWithIdentifier.PacketIdentifier.Value;
|
||||
}
|
||||
|
||||
var packetAwaiter = _packetDispatcher.AddPacketAwaiter<TResponsePacket>(identifier);
|
||||
try
|
||||
{
|
||||
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, new[] { requestPacket }, cancellationToken).ConfigureAwait(false);
|
||||
var respone = await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, _options.CommunicationTimeout, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return (TResponsePacket)respone;
|
||||
}
|
||||
catch (MqttCommunicationTimedOutException)
|
||||
{
|
||||
_logger.Warning<MqttPacketDispatcher>($"Timeout while waiting for packet of type '{typeof(TResponsePacket).Namespace}'.");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_packetDispatcher.RemovePacketAwaiter<TResponsePacket>(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendKeepAliveMessagesAsync()
|
||||
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Verbose<MqttClient>("Start sending keep alive packets.");
|
||||
|
||||
try
|
||||
{
|
||||
while (!_cancellationTokenSource.Token.IsCancellationRequested)
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
|
||||
if (_options.KeepAliveSendInterval.HasValue)
|
||||
@@ -421,10 +354,10 @@ namespace MQTTnet.Client
|
||||
|
||||
if (_sendTracker.Elapsed > keepAliveSendInterval)
|
||||
{
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket(), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
await Task.Delay(keepAliveSendInterval, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
@@ -440,7 +373,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
_logger.Error<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
|
||||
}
|
||||
|
||||
|
||||
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
@@ -449,24 +382,34 @@ namespace MQTTnet.Client
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceivePacketsAsync()
|
||||
private async Task ReceivePacketsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Verbose<MqttClient>("Start receiving packets.");
|
||||
|
||||
try
|
||||
{
|
||||
while (!_cancellationTokenSource.Token.IsCancellationRequested)
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_isReceivingPackets = true;
|
||||
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
if (_cancellationTokenSource.Token.IsCancellationRequested)
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartProcessReceivedPacket(packet);
|
||||
if (packet == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_options.ReceivedApplicationMessageProcessingMode == MqttReceivedApplicationMessageProcessingMode.SingleThread)
|
||||
{
|
||||
await ProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (_options.ReceivedApplicationMessageProcessingMode == MqttReceivedApplicationMessageProcessingMode.DedicatedThread)
|
||||
{
|
||||
StartProcessReceivedPacketAsync(packet, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
@@ -484,6 +427,7 @@ namespace MQTTnet.Client
|
||||
}
|
||||
|
||||
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
|
||||
_packetDispatcher.Dispatch(exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -491,26 +435,133 @@ namespace MQTTnet.Client
|
||||
}
|
||||
}
|
||||
|
||||
private void StartProcessReceivedPacket(MqttBasePacket packet)
|
||||
private async Task ProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
Task.Run(() => ProcessReceivedPacketAsync(packet), _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
private async Task StartReceivingPacketsAsync()
|
||||
{
|
||||
_isReceivingPackets = false;
|
||||
|
||||
_packetReceiverTask = Task.Run(ReceivePacketsAsync, _cancellationTokenSource.Token);
|
||||
|
||||
while (!_isReceivingPackets && !_cancellationTokenSource.Token.IsCancellationRequested)
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(100), _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
{
|
||||
await ProcessReceivedPublishPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet is MqttPingReqPacket)
|
||||
{
|
||||
await SendAsync(new MqttPingRespPacket(), cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet is MqttDisconnectPacket)
|
||||
{
|
||||
await DisconnectAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet is MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
await ProcessReceivedPubRelPacket(pubRelPacket, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_packetDispatcher.Dispatch(packet);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClient>(exception, "Unhandled exception while processing received packet.");
|
||||
}
|
||||
}
|
||||
|
||||
private void StartSendingKeepAliveMessages()
|
||||
private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
_keepAliveMessageSenderTask = Task.Run(SendKeepAliveMessagesAsync, _cancellationTokenSource.Token);
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
{
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, cancellationToken);
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
|
||||
{
|
||||
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, cancellationToken);
|
||||
}
|
||||
|
||||
throw new MqttCommunicationException("Received a not supported QoS level.");
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = new MqttPubCompPacket
|
||||
{
|
||||
PacketIdentifier = pubRelPacket.PacketIdentifier
|
||||
};
|
||||
|
||||
return SendAsync(response, cancellationToken);
|
||||
}
|
||||
|
||||
private void StartReceivingPackets(CancellationToken cancellationToken)
|
||||
{
|
||||
_packetReceiverTask = Task.Factory.StartNew(
|
||||
() => ReceivePacketsAsync(cancellationToken),
|
||||
cancellationToken,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Current);
|
||||
}
|
||||
|
||||
private void StartSendingKeepAliveMessages(CancellationToken cancellationToken)
|
||||
{
|
||||
_keepAliveMessageSenderTask = Task.Factory.StartNew(
|
||||
() => SendKeepAliveMessagesAsync(cancellationToken),
|
||||
cancellationToken,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Current);
|
||||
}
|
||||
|
||||
private void StartProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
Task.Run(() => ProcessReceivedPacketAsync(packet, cancellationToken), cancellationToken);
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
}
|
||||
|
||||
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
|
||||
{
|
||||
try
|
||||
{
|
||||
var applicationMessage = publishPacket.ToApplicationMessage();
|
||||
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(_options.ClientId, applicationMessage));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClient>(exception, "Unhandled exception while handling application message.");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task WaitForTaskAsync(Task task, Task sender)
|
||||
{
|
||||
if (task == sender || task == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (task.IsCanceled || task.IsCompleted || task.IsFaulted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,22 +5,17 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttClientOptions : IMqttClientOptions
|
||||
{
|
||||
public MqttApplicationMessage WillMessage { get; set; }
|
||||
|
||||
public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
|
||||
|
||||
public bool CleanSession { get; set; } = true;
|
||||
|
||||
public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials();
|
||||
|
||||
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
|
||||
|
||||
public TimeSpan? KeepAliveSendInterval { get; set; }
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||
public IMqttClientChannelOptions ChannelOptions { get; set; }
|
||||
|
||||
public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
|
||||
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
|
||||
public TimeSpan? KeepAliveSendInterval { get; set; }
|
||||
public MqttReceivedApplicationMessageProcessingMode ReceivedApplicationMessageProcessingMode { get; set; } = MqttReceivedApplicationMessageProcessingMode.SingleThread;
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||
|
||||
public IMqttClientChannelOptions ChannelOptions { get; set; }
|
||||
public MqttApplicationMessage WillMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,12 @@ namespace MQTTnet.Client
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithKeepAliveSendInterval(TimeSpan value)
|
||||
{
|
||||
_options.KeepAliveSendInterval = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithClientId(string value)
|
||||
{
|
||||
_options.ClientId = value;
|
||||
@@ -108,6 +114,13 @@ namespace MQTTnet.Client
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithReceivedApplicationMessageProcessingMode(
|
||||
MqttReceivedApplicationMessageProcessingMode mode)
|
||||
{
|
||||
_options.ReceivedApplicationMessageProcessingMode = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IMqttClientOptions Build()
|
||||
{
|
||||
if (_tlsOptions != null)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
public int? Port { get; set; }
|
||||
|
||||
public int BufferSize { get; set; } = 20 * 4096;
|
||||
public int BufferSize { get; set; } = 4096;
|
||||
|
||||
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace MQTTnet.Client
|
||||
|
||||
public CookieContainer CookieContainer { get; set; }
|
||||
|
||||
public int BufferSize { get; set; } = 4096;
|
||||
|
||||
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttPacketDispatcher
|
||||
{
|
||||
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _awaiters = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>();
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
public MqttPacketDispatcher(IMqttNetLogger logger)
|
||||
private readonly ConcurrentDictionary<Tuple<ushort, Type>, TaskCompletionSource<MqttBasePacket>> _awaiters = new ConcurrentDictionary<Tuple<ushort, Type>, TaskCompletionSource<MqttBasePacket>>();
|
||||
|
||||
public void Dispatch(Exception exception)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<MqttBasePacket> WaitForPacketAsync(Type responseType, ushort? identifier, TimeSpan timeout)
|
||||
{
|
||||
var packetAwaiter = AddPacketAwaiter(responseType, identifier);
|
||||
try
|
||||
foreach (var awaiter in _awaiters)
|
||||
{
|
||||
return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (MqttCommunicationTimedOutException)
|
||||
{
|
||||
_logger.Warning<MqttPacketDispatcher>("Timeout while waiting for packet of type '{0}'.", responseType.Name);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
RemovePacketAwaiter(responseType, identifier);
|
||||
awaiter.Value.SetException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,21 +21,19 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
|
||||
var type = packet.GetType();
|
||||
|
||||
if (_awaiters.TryGetValue(type, out var byId))
|
||||
ushort identifier = 0;
|
||||
if (packet is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue)
|
||||
{
|
||||
ushort? identifier = 0;
|
||||
if (packet is IMqttPacketWithIdentifier packetWithIdentifier)
|
||||
{
|
||||
identifier = packetWithIdentifier.PacketIdentifier;
|
||||
}
|
||||
identifier = packetWithIdentifier.PacketIdentifier.Value;
|
||||
}
|
||||
|
||||
if (byId.TryRemove(identifier.Value, out var tcs))
|
||||
{
|
||||
tcs.TrySetResult(packet);
|
||||
return;
|
||||
}
|
||||
var type = packet.GetType();
|
||||
var key = new Tuple<ushort, Type>(identifier, type);
|
||||
|
||||
if (_awaiters.TryRemove(key, out var tcs))
|
||||
{
|
||||
tcs.TrySetResult(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched.");
|
||||
@@ -65,7 +44,7 @@ namespace MQTTnet.Client
|
||||
_awaiters.Clear();
|
||||
}
|
||||
|
||||
private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(Type responseType, ushort? identifier)
|
||||
public TaskCompletionSource<MqttBasePacket> AddPacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
|
||||
{
|
||||
var tcs = new TaskCompletionSource<MqttBasePacket>();
|
||||
|
||||
@@ -73,25 +52,25 @@ namespace MQTTnet.Client
|
||||
{
|
||||
identifier = 0;
|
||||
}
|
||||
|
||||
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
|
||||
if (!byId.TryAdd(identifier.Value, tcs))
|
||||
|
||||
var key = new Tuple<ushort, Type>(identifier ?? 0, typeof(TResponsePacket));
|
||||
if (!_awaiters.TryAdd(key, tcs))
|
||||
{
|
||||
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}.");
|
||||
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{key.Item2.Name}' with identifier {key.Item1}.");
|
||||
}
|
||||
|
||||
return tcs;
|
||||
}
|
||||
|
||||
private void RemovePacketAwaiter(Type responseType, ushort? identifier)
|
||||
public void RemovePacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
|
||||
{
|
||||
if (!identifier.HasValue)
|
||||
{
|
||||
identifier = 0;
|
||||
}
|
||||
|
||||
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
|
||||
byId.TryRemove(identifier.Value, out var _);
|
||||
var key = new Tuple<ushort, Type>(identifier ?? 0, typeof(TResponsePacket));
|
||||
_awaiters.TryRemove(key, out var _);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public enum MqttReceivedApplicationMessageProcessingMode
|
||||
{
|
||||
SingleThread,
|
||||
DedicatedThread
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
namespace MQTTnet.Exceptions
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Exceptions
|
||||
{
|
||||
public sealed class MqttCommunicationTimedOutException : MqttCommunicationException
|
||||
{
|
||||
public MqttCommunicationTimedOutException() { }
|
||||
public MqttCommunicationTimedOutException(Exception innerException) : base(innerException) { }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Networking;
|
||||
using Windows.Networking.Sockets;
|
||||
@@ -17,17 +18,20 @@ namespace MQTTnet.Implementations
|
||||
{
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
|
||||
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
|
||||
public static int BufferSize { get; set; } = 4096; // Can be changed for fine tuning by library user.
|
||||
|
||||
private readonly int _bufferSize = BufferSize;
|
||||
private readonly MqttClientTcpOptions _options;
|
||||
|
||||
private StreamSocket _socket;
|
||||
private Stream _readStream;
|
||||
private Stream _writeStream;
|
||||
|
||||
public MqttTcpChannel(MqttClientTcpOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_bufferSize = _options.BufferSize;
|
||||
|
||||
_bufferSize = options.BufferSize;
|
||||
}
|
||||
|
||||
public MqttTcpChannel(StreamSocket socket)
|
||||
@@ -37,16 +41,15 @@ namespace MQTTnet.Implementations
|
||||
CreateStreams();
|
||||
}
|
||||
|
||||
public Stream SendStream { get; private set; }
|
||||
public Stream ReceiveStream { get; private set; }
|
||||
|
||||
public static Func<MqttClientTcpOptions, IEnumerable<ChainValidationResult>> CustomIgnorableServerCertificateErrorsResolver { get; set; }
|
||||
|
||||
public async Task ConnectAsync()
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
_socket = new StreamSocket();
|
||||
_socket.Control.NoDelay = true;
|
||||
_socket.Control.KeepAlive = true;
|
||||
}
|
||||
|
||||
if (!_options.TlsOptions.UseTls)
|
||||
@@ -74,11 +77,22 @@ namespace MQTTnet.Implementations
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _readStream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
await _writeStream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
await _writeStream.FlushAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
SendStream?.Dispose();
|
||||
_readStream?.Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
@@ -88,12 +102,12 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
finally
|
||||
{
|
||||
SendStream = null;
|
||||
_readStream = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ReceiveStream?.Dispose();
|
||||
_writeStream?.Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
@@ -103,7 +117,7 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReceiveStream = null;
|
||||
_writeStream = null;
|
||||
}
|
||||
|
||||
try
|
||||
@@ -122,12 +136,6 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateStreams()
|
||||
{
|
||||
SendStream = _socket.OutputStream.AsStreamForWrite(_bufferSize);
|
||||
ReceiveStream = _socket.InputStream.AsStreamForRead(_bufferSize);
|
||||
}
|
||||
|
||||
private static Certificate LoadCertificate(MqttClientTcpOptions options)
|
||||
{
|
||||
if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any())
|
||||
@@ -171,6 +179,12 @@ namespace MQTTnet.Implementations
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void CreateStreams()
|
||||
{
|
||||
_readStream = _socket.InputStream.AsStreamForRead(_bufferSize);
|
||||
_writeStream = _socket.OutputStream.AsStreamForWrite(_bufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -7,6 +7,7 @@ using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Client;
|
||||
|
||||
@@ -14,20 +15,10 @@ namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttTcpChannel : IMqttChannel
|
||||
{
|
||||
#if NET452 || NET461
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
|
||||
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
|
||||
|
||||
private readonly int _bufferSize = BufferSize;
|
||||
#else
|
||||
private readonly int _bufferSize = 0;
|
||||
#endif
|
||||
|
||||
private readonly MqttClientTcpOptions _options;
|
||||
|
||||
private Socket _socket;
|
||||
private SslStream _sslStream;
|
||||
private Stream _stream;
|
||||
|
||||
/// <summary>
|
||||
/// called on client sockets are created in connect
|
||||
@@ -35,7 +26,6 @@ namespace MQTTnet.Implementations
|
||||
public MqttTcpChannel(MqttClientTcpOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_bufferSize = options.BufferSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,21 +35,17 @@ namespace MQTTnet.Implementations
|
||||
public MqttTcpChannel(Socket socket, SslStream sslStream)
|
||||
{
|
||||
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
|
||||
_sslStream = sslStream;
|
||||
|
||||
CreateStreams();
|
||||
CreateStream(sslStream);
|
||||
}
|
||||
|
||||
public Stream SendStream { get; private set; }
|
||||
public Stream ReceiveStream { get; private set; }
|
||||
|
||||
public static Func<X509Certificate, X509Chain, SslPolicyErrors, MqttClientTcpOptions, bool> CustomCertificateValidationCallback { get; set; }
|
||||
|
||||
public async Task ConnectAsync()
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
|
||||
}
|
||||
|
||||
#if NET452 || NET461
|
||||
@@ -68,13 +54,14 @@ namespace MQTTnet.Implementations
|
||||
await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false);
|
||||
#endif
|
||||
|
||||
SslStream sslStream = null;
|
||||
if (_options.TlsOptions.UseTls)
|
||||
{
|
||||
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
|
||||
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
|
||||
sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
|
||||
await sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
CreateStreams();
|
||||
|
||||
CreateStream(sslStream);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
@@ -83,13 +70,21 @@ namespace MQTTnet.Implementations
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _stream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _stream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var oneStreamIsUsed = SendStream != null && ReceiveStream != null && ReferenceEquals(SendStream, ReceiveStream);
|
||||
|
||||
try
|
||||
{
|
||||
SendStream?.Dispose();
|
||||
_stream?.Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
@@ -99,40 +94,7 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
finally
|
||||
{
|
||||
SendStream = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!oneStreamIsUsed)
|
||||
{
|
||||
ReceiveStream?.Dispose();
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReceiveStream = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_sslStream?.Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sslStream = null;
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
try
|
||||
@@ -198,25 +160,16 @@ namespace MQTTnet.Implementations
|
||||
return certificates;
|
||||
}
|
||||
|
||||
private void CreateStreams()
|
||||
private void CreateStream(Stream stream)
|
||||
{
|
||||
Stream stream;
|
||||
if (_sslStream != null)
|
||||
if (stream != null)
|
||||
{
|
||||
stream = _sslStream;
|
||||
_stream = stream;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream = new NetworkStream(_socket, true);
|
||||
_stream = new NetworkStream(_socket, true);
|
||||
}
|
||||
|
||||
#if NET452 || NET461
|
||||
SendStream = new BufferedStream(stream, _bufferSize);
|
||||
ReceiveStream = new BufferedStream(stream, _bufferSize);
|
||||
#else
|
||||
SendStream = stream;
|
||||
ReceiveStream = stream;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable
|
||||
public class MqttTcpServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
private readonly IMqttNetLogger _logger;
|
||||
private StreamSocketListener _defaultEndpointSocket;
|
||||
@@ -26,12 +26,19 @@ namespace MQTTnet.Implementations
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (_defaultEndpointSocket != null) throw new InvalidOperationException("Server is already started.");
|
||||
|
||||
|
||||
if (options.DefaultEndpointOptions.IsEnabled)
|
||||
{
|
||||
_defaultEndpointSocket = new StreamSocketListener();
|
||||
await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket);
|
||||
|
||||
// This also affects the client sockets.
|
||||
_defaultEndpointSocket.Control.NoDelay = true;
|
||||
_defaultEndpointSocket.Control.KeepAlive = true;
|
||||
_defaultEndpointSocket.Control.QualityOfService = SocketQualityOfService.LowLatency;
|
||||
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync;
|
||||
|
||||
await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket);
|
||||
|
||||
}
|
||||
|
||||
if (options.TlsEndpointOptions.IsEnabled)
|
||||
@@ -55,7 +62,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopAsync();
|
||||
StopAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private void AcceptDefaultEndpointConnectionsAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
|
||||
|
||||
@@ -14,7 +14,7 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable
|
||||
public class MqttTcpServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
@@ -38,11 +38,16 @@ namespace MQTTnet.Implementations
|
||||
|
||||
if (options.DefaultEndpointOptions.IsEnabled)
|
||||
{
|
||||
_defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
_defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
|
||||
|
||||
_defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort()));
|
||||
_defaultEndpointSocket.Listen(options.ConnectionBacklog);
|
||||
|
||||
Task.Run(async () => await AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
Task.Factory.StartNew(
|
||||
() => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token),
|
||||
_cancellationTokenSource.Token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Current);
|
||||
}
|
||||
|
||||
if (options.TlsEndpointOptions.IsEnabled)
|
||||
@@ -62,7 +67,11 @@ namespace MQTTnet.Implementations
|
||||
_tlsEndpointSocket.Bind(new IPEndPoint(options.TlsEndpointOptions.BoundIPAddress, options.GetTlsEndpointPort()));
|
||||
_tlsEndpointSocket.Listen(options.ConnectionBacklog);
|
||||
|
||||
Task.Run(async () => await AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
Task.Factory.StartNew(
|
||||
() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token),
|
||||
_cancellationTokenSource.Token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Current);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
@@ -78,7 +87,7 @@ namespace MQTTnet.Implementations
|
||||
_defaultEndpointSocket = null;
|
||||
|
||||
_tlsCertificate = null;
|
||||
|
||||
|
||||
_tlsEndpointSocket?.Dispose();
|
||||
_tlsEndpointSocket = null;
|
||||
|
||||
@@ -87,7 +96,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopAsync();
|
||||
StopAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken)
|
||||
@@ -102,6 +111,7 @@ namespace MQTTnet.Implementations
|
||||
#else
|
||||
var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false);
|
||||
#endif
|
||||
clientSocket.NoDelay = true;
|
||||
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger);
|
||||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
|
||||
@@ -137,7 +147,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
var sslStream = new SslStream(new NetworkStream(clientSocket));
|
||||
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
|
||||
|
||||
|
||||
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger);
|
||||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
@@ -11,22 +10,22 @@ namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttWebSocketChannel : IMqttChannel
|
||||
{
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
|
||||
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
|
||||
|
||||
private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
|
||||
private readonly MqttClientWebSocketOptions _options;
|
||||
private ClientWebSocket _webSocket;
|
||||
|
||||
private WebSocket _webSocket;
|
||||
|
||||
public MqttWebSocketChannel(MqttClientWebSocketOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public Stream SendStream { get; private set; }
|
||||
public Stream ReceiveStream { get; private set; }
|
||||
public MqttWebSocketChannel(WebSocket webSocket)
|
||||
{
|
||||
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var uri = _options.Uri;
|
||||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -41,42 +40,40 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
_webSocket = new ClientWebSocket();
|
||||
|
||||
var clientWebSocket = new ClientWebSocket();
|
||||
|
||||
if (_options.RequestHeaders != null)
|
||||
{
|
||||
foreach (var requestHeader in _options.RequestHeaders)
|
||||
{
|
||||
_webSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
|
||||
}
|
||||
clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (_options.SubProtocols != null)
|
||||
{
|
||||
foreach (var subProtocol in _options.SubProtocols)
|
||||
{
|
||||
_webSocket.Options.AddSubProtocol(subProtocol);
|
||||
clientWebSocket.Options.AddSubProtocol(subProtocol);
|
||||
}
|
||||
}
|
||||
|
||||
if (_options.CookieContainer != null)
|
||||
{
|
||||
_webSocket.Options.Cookies = _options.CookieContainer;
|
||||
clientWebSocket.Options.Cookies = _options.CookieContainer;
|
||||
}
|
||||
|
||||
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
|
||||
{
|
||||
_webSocket.Options.ClientCertificates = new X509CertificateCollection();
|
||||
clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
|
||||
foreach (var certificate in _options.TlsOptions.Certificates)
|
||||
{
|
||||
_webSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
|
||||
clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
|
||||
}
|
||||
}
|
||||
|
||||
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
SendStream = new WebSocketStream(_webSocket);
|
||||
ReceiveStream = SendStream;
|
||||
await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false);
|
||||
_webSocket = clientWebSocket;
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
@@ -94,8 +91,32 @@ namespace MQTTnet.Implementations
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
|
||||
return response.Count;
|
||||
}
|
||||
|
||||
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
// This lock is required because the client will throw an exception if _SendAsync_ is
|
||||
// called from multiple threads at the same time. But this issue only happens with several
|
||||
// framework versions.
|
||||
await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sendLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_sendLock?.Dispose();
|
||||
|
||||
try
|
||||
{
|
||||
_webSocket?.Dispose();
|
||||
@@ -106,7 +127,7 @@ namespace MQTTnet.Implementations
|
||||
finally
|
||||
{
|
||||
_webSocket = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Exceptions;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class WebSocketStream : Stream
|
||||
{
|
||||
private readonly byte[] _chunckBuffer = new byte[MqttWebSocketChannel.BufferSize];
|
||||
private readonly Queue<byte> _buffer = new Queue<byte>(MqttWebSocketChannel.BufferSize);
|
||||
private readonly WebSocket _webSocket;
|
||||
|
||||
public WebSocketStream(WebSocket webSocket)
|
||||
{
|
||||
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var bytesRead = 0;
|
||||
|
||||
// Use existing date from buffer.
|
||||
while (count > 0 && _buffer.Any())
|
||||
{
|
||||
buffer[offset] = _buffer.Dequeue();
|
||||
count--;
|
||||
bytesRead++;
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
// Fetch new data if the buffer is not full.
|
||||
while (_webSocket.State == WebSocketState.Open)
|
||||
{
|
||||
await FetchChunkAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
while (count > 0 && _buffer.Any())
|
||||
{
|
||||
buffer[offset] = _buffer.Dequeue();
|
||||
count--;
|
||||
bytesRead++;
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return bytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
if (_webSocket.State == WebSocketState.Closed)
|
||||
{
|
||||
throw new MqttCommunicationException("WebSocket connection closed.");
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private async Task FetchChunkAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (response.MessageType == WebSocketMessageType.Binary)
|
||||
{
|
||||
for (var i = 0; i < response.Count; i++)
|
||||
{
|
||||
_buffer.Enqueue(_chunckBuffer[i]);
|
||||
}
|
||||
}
|
||||
else if (response.MessageType == WebSocketMessageType.Text)
|
||||
{
|
||||
throw new MqttProtocolViolationException("WebSocket channel received TEXT message.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
public sealed class AsyncAutoResetEvent : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0, 1);
|
||||
|
||||
public Task WaitOneAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return _semaphore.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public void Set()
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_semaphore?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
public sealed class AsyncLock : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
public Task EnterAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return _semaphore.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public void Exit()
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_semaphore?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
internal static class MqttApplicationMessageExtensions
|
||||
public static class MqttApplicationMessageExtensions
|
||||
{
|
||||
public static MqttApplicationMessage ToApplicationMessage(this MqttPublishPacket publishPacket)
|
||||
{
|
||||
|
||||
@@ -7,72 +7,52 @@ namespace MQTTnet.Internal
|
||||
{
|
||||
public static class TaskExtensions
|
||||
{
|
||||
public static async Task TimeoutAfter(this Task task, TimeSpan timeout)
|
||||
public static async Task TimeoutAfter(Func<CancellationToken, Task> action, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (task == null) throw new ArgumentNullException(nameof(task));
|
||||
if (action == null) throw new ArgumentNullException(nameof(action));
|
||||
|
||||
using (var timeoutCts = new CancellationTokenSource())
|
||||
using (var timeoutCts = new CancellationTokenSource(timeout))
|
||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeoutTask = Task.Delay(timeout, timeoutCts.Token);
|
||||
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
|
||||
|
||||
if (finishedTask == timeoutTask)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException();
|
||||
}
|
||||
|
||||
if (task.IsCanceled)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
throw new MqttCommunicationException(task.Exception?.GetBaseException());
|
||||
}
|
||||
await action(linkedCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
catch (OperationCanceledException exception)
|
||||
{
|
||||
timeoutCts.Cancel();
|
||||
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
||||
if (timeoutReached)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException(exception);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout)
|
||||
public static async Task<TResult> TimeoutAfter<TResult>(Func<CancellationToken, Task<TResult>> action, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (task == null) throw new ArgumentNullException(nameof(task));
|
||||
if (action == null) throw new ArgumentNullException(nameof(action));
|
||||
|
||||
using (var timeoutCts = new CancellationTokenSource())
|
||||
using (var timeoutCts = new CancellationTokenSource(timeout))
|
||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeoutTask = Task.Delay(timeout, timeoutCts.Token);
|
||||
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
|
||||
|
||||
if (finishedTask == timeoutTask)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException();
|
||||
}
|
||||
|
||||
if (task.IsCanceled)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
throw new MqttCommunicationException(task.Exception.GetBaseException());
|
||||
}
|
||||
|
||||
return task.Result;
|
||||
return await action(linkedCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
catch (OperationCanceledException exception)
|
||||
{
|
||||
timeoutCts.Cancel();
|
||||
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
||||
if (timeoutReached)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException(exception);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
|
||||
namespace MQTTnet.Core.Internal
|
||||
{
|
||||
public class TestMqttChannel : IMqttChannel
|
||||
{
|
||||
private readonly MemoryStream _stream;
|
||||
|
||||
public TestMqttChannel(MemoryStream stream)
|
||||
{
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _stream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _stream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
<AssemblyName>MQTTnet</AssemblyName>
|
||||
<RootNamespace>MQTTnet</RootNamespace>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
<DebugType>Full</DebugType>
|
||||
<AssemblyVersion>0.0.0.0</AssemblyVersion>
|
||||
<FileVersion>0.0.0.0</FileVersion>
|
||||
<Version>0.0.0.0</Version>
|
||||
@@ -19,7 +18,7 @@
|
||||
<DelaySign>false</DelaySign>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'">
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
@@ -32,6 +31,10 @@
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DebugType>Full</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
|
||||
@@ -43,17 +46,17 @@
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.2" />
|
||||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.2" />
|
||||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'">
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="5.4.1" />
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net452'">
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace MQTTnet.ManagedClient
|
||||
_connectionCancellationToken = new CancellationTokenSource();
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
Task.Run(async () => await MaintainConnectionAsync(_connectionCancellationToken.Token).ConfigureAwait(false), _connectionCancellationToken.Token).ConfigureAwait(false);
|
||||
Task.Run(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token);
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
|
||||
_logger.Info<ManagedMqttClient>("Started");
|
||||
@@ -190,10 +190,7 @@ namespace MQTTnet.ManagedClient
|
||||
if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
|
||||
{
|
||||
await SynchronizeSubscriptionsAsync().ConfigureAwait(false);
|
||||
|
||||
StartPublishing();
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -375,7 +372,7 @@ namespace MQTTnet.ManagedClient
|
||||
_publishingCancellationToken = cts;
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
Task.Run(async () => await PublishQueuedMessagesAsync(cts.Token).ConfigureAwait(false), cts.Token).ConfigureAwait(false);
|
||||
Task.Run(() => PublishQueuedMessagesAsync(cts.Token), cts.Token);
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,12 @@ namespace MQTTnet
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttApplicationMessageBuilder WithPayload(MemoryStream payload)
|
||||
public MqttApplicationMessageBuilder WithPayload(Stream payload)
|
||||
{
|
||||
return WithPayload(payload, payload.Length - payload.Position);
|
||||
}
|
||||
|
||||
public MqttApplicationMessageBuilder WithPayload(Stream payload, long length)
|
||||
{
|
||||
if (payload == null)
|
||||
{
|
||||
@@ -46,7 +51,7 @@ namespace MQTTnet
|
||||
}
|
||||
else
|
||||
{
|
||||
_payload = new byte[payload.Length - payload.Position];
|
||||
_payload = new byte[length];
|
||||
payload.Read(_payload, 0, _payload.Length);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Packets
|
||||
public override string ToString()
|
||||
{
|
||||
var topicFiltersText = string.Join(",", TopicFilters);
|
||||
return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
|
||||
return "Unsubscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
@@ -9,8 +8,8 @@ namespace MQTTnet.Serializer
|
||||
{
|
||||
MqttProtocolVersion ProtocolVersion { get; set; }
|
||||
|
||||
ICollection<ArraySegment<byte>> Serialize(MqttBasePacket mqttPacket);
|
||||
ArraySegment<byte> Serialize(MqttBasePacket mqttPacket);
|
||||
|
||||
MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body);
|
||||
MqttBasePacket Deserialize(MqttPacketHeader header, Stream body);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
@@ -13,7 +13,7 @@ namespace MQTTnet.Serializer
|
||||
public sealed class MqttPacketReader : BinaryReader
|
||||
{
|
||||
private readonly MqttPacketHeader _header;
|
||||
|
||||
|
||||
public MqttPacketReader(MqttPacketHeader header, Stream bodyStream)
|
||||
: base(bodyStream, Encoding.UTF8, true)
|
||||
{
|
||||
@@ -22,7 +22,7 @@ namespace MQTTnet.Serializer
|
||||
|
||||
public bool EndOfRemainingData => BaseStream.Position == _header.BodyLength;
|
||||
|
||||
public static async Task<MqttPacketHeader> ReadHeaderAsync(Stream stream, CancellationToken cancellationToken)
|
||||
public static async Task<MqttPacketHeader> ReadHeaderAsync(IMqttChannel stream, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
@@ -33,7 +33,7 @@ namespace MQTTnet.Serializer
|
||||
// some large delay and thus the thread should be put back to the pool (await). So ReadByte()
|
||||
// is not an option here.
|
||||
var buffer = new byte[1];
|
||||
var readCount = await stream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
|
||||
var readCount = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
if (readCount <= 0)
|
||||
{
|
||||
return null;
|
||||
@@ -89,15 +89,14 @@ namespace MQTTnet.Serializer
|
||||
return ReadBytes(_header.BodyLength - (int)BaseStream.Position);
|
||||
}
|
||||
|
||||
private static async Task<int> ReadBodyLengthAsync(Stream stream, CancellationToken cancellationToken)
|
||||
private static async Task<int> ReadBodyLengthAsync(IMqttChannel stream, CancellationToken cancellationToken)
|
||||
{
|
||||
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
||||
var multiplier = 1;
|
||||
var value = 0;
|
||||
byte encodedByte;
|
||||
|
||||
int encodedByte;
|
||||
var buffer = new byte[1];
|
||||
var readBytes = new List<byte>();
|
||||
|
||||
do
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
@@ -112,12 +111,11 @@ namespace MQTTnet.Serializer
|
||||
}
|
||||
|
||||
encodedByte = buffer[0];
|
||||
readBytes.Add(encodedByte);
|
||||
|
||||
value += (byte)(encodedByte & 127) * multiplier;
|
||||
if (multiplier > 128 * 128 * 128)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)}).");
|
||||
throw new MqttProtocolViolationException("Remaining length is invalid.");
|
||||
}
|
||||
|
||||
multiplier *= 128;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -12,37 +11,42 @@ namespace MQTTnet.Serializer
|
||||
public sealed class MqttPacketSerializer : IMqttPacketSerializer
|
||||
{
|
||||
private static byte[] ProtocolVersionV311Name { get; } = Encoding.UTF8.GetBytes("MQTT");
|
||||
private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIs");
|
||||
private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIsdp");
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||
|
||||
public ICollection<ArraySegment<byte>> Serialize(MqttBasePacket packet)
|
||||
public ArraySegment<byte> Serialize(MqttBasePacket packet)
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
|
||||
using (var stream = new MemoryStream(128))
|
||||
using (var writer = new MqttPacketWriter(stream))
|
||||
{
|
||||
// Leave enough head space for max header size (fixed + 4 variable remaining length)
|
||||
stream.Position = 5;
|
||||
var fixedHeader = SerializePacket(packet, writer);
|
||||
var remainingLength = (int)stream.Length;
|
||||
|
||||
stream.Position = 1;
|
||||
var remainingLength = MqttPacketWriter.EncodeRemainingLength((int)stream.Length - 5, stream);
|
||||
|
||||
var headerSize = remainingLength + 1;
|
||||
var headerOffset = 5 - headerSize;
|
||||
|
||||
// Position cursor on correct offset on beginining of array (has leading 0x0)
|
||||
stream.Position = headerOffset;
|
||||
|
||||
writer.Write(fixedHeader);
|
||||
MqttPacketWriter.WriteRemainingLength(remainingLength, writer);
|
||||
var headerLength = (int)stream.Length - remainingLength;
|
||||
|
||||
#if NET461 || NET452 || NETSTANDARD2_0
|
||||
var buffer = stream.GetBuffer();
|
||||
#else
|
||||
var buffer = stream.ToArray();
|
||||
#endif
|
||||
return new List<ArraySegment<byte>>
|
||||
{
|
||||
new ArraySegment<byte>(buffer, remainingLength, headerLength),
|
||||
new ArraySegment<byte>(buffer, 0, remainingLength)
|
||||
};
|
||||
return new ArraySegment<byte>(buffer, headerOffset, (int)stream.Length - headerOffset);
|
||||
}
|
||||
}
|
||||
|
||||
public MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body)
|
||||
public MqttBasePacket Deserialize(MqttPacketHeader header, Stream body)
|
||||
{
|
||||
if (header == null) throw new ArgumentNullException(nameof(header));
|
||||
if (body == null) throw new ArgumentNullException(nameof(body));
|
||||
@@ -178,7 +182,7 @@ namespace MQTTnet.Serializer
|
||||
|
||||
var topic = reader.ReadStringWithLengthPrefix();
|
||||
|
||||
ushort packetIdentifier = 0;
|
||||
ushort? packetIdentifier = null;
|
||||
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
packetIdentifier = reader.ReadUInt16();
|
||||
@@ -186,12 +190,12 @@ namespace MQTTnet.Serializer
|
||||
|
||||
var packet = new MqttPublishPacket
|
||||
{
|
||||
PacketIdentifier = packetIdentifier,
|
||||
Retain = retain,
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
Dup = dup,
|
||||
Topic = topic,
|
||||
Payload = reader.ReadRemainingData(),
|
||||
PacketIdentifier = packetIdentifier
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
Dup = dup
|
||||
};
|
||||
|
||||
return packet;
|
||||
@@ -199,22 +203,30 @@ namespace MQTTnet.Serializer
|
||||
|
||||
private static MqttBasePacket DeserializeConnect(MqttPacketReader reader)
|
||||
{
|
||||
reader.ReadBytes(2); // Skip 2 bytes
|
||||
reader.ReadBytes(2); // Skip 2 bytes for header and remaining length.
|
||||
|
||||
MqttProtocolVersion protocolVersion;
|
||||
var protocolName = reader.ReadBytes(4);
|
||||
if (protocolName.SequenceEqual(ProtocolVersionV310Name))
|
||||
{
|
||||
reader.ReadBytes(2);
|
||||
protocolVersion = MqttProtocolVersion.V310;
|
||||
}
|
||||
else if (protocolName.SequenceEqual(ProtocolVersionV311Name))
|
||||
|
||||
if (protocolName.SequenceEqual(ProtocolVersionV311Name))
|
||||
{
|
||||
protocolVersion = MqttProtocolVersion.V311;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new MqttProtocolViolationException("Protocol name is not supported.");
|
||||
var buffer = new byte[6];
|
||||
Array.Copy(protocolName, buffer, 4);
|
||||
protocolName = reader.ReadBytes(2);
|
||||
Array.Copy(protocolName, 0, buffer, 4, 2);
|
||||
|
||||
if (protocolName.SequenceEqual(ProtocolVersionV310Name))
|
||||
{
|
||||
protocolVersion = MqttProtocolVersion.V310;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new MqttProtocolViolationException("Protocol name is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
reader.ReadByte(); // Skip protocol level
|
||||
@@ -293,7 +305,7 @@ namespace MQTTnet.Serializer
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
||||
private static void ValidateConnectPacket(MqttConnectPacket packet)
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
@@ -319,16 +331,15 @@ namespace MQTTnet.Serializer
|
||||
ValidateConnectPacket(packet);
|
||||
|
||||
// Write variable header
|
||||
writer.Write(0x00, 0x04); // 3.1.2.1 Protocol Name
|
||||
if (ProtocolVersion == MqttProtocolVersion.V311)
|
||||
{
|
||||
writer.Write(ProtocolVersionV311Name);
|
||||
writer.Write(0x04); // 3.1.2.2 Protocol Level (4)
|
||||
writer.WriteWithLengthPrefix(ProtocolVersionV311Name);
|
||||
writer.Write(0x04); // 3.1.2.2 Protocol Level 4
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(ProtocolVersionV310Name);
|
||||
writer.Write(0x64, 0x70, 0x03); // Protocol Level (0x03)
|
||||
writer.WriteWithLengthPrefix(ProtocolVersionV310Name);
|
||||
writer.Write(0x03); // Protocol Level 3
|
||||
}
|
||||
|
||||
var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags
|
||||
@@ -347,6 +358,11 @@ namespace MQTTnet.Serializer
|
||||
connectFlags.Write(false);
|
||||
}
|
||||
|
||||
if (packet.Password != null && packet.Username == null)
|
||||
{
|
||||
throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22].");
|
||||
}
|
||||
|
||||
connectFlags.Write(packet.Password != null);
|
||||
connectFlags.Write(packet.Username != null);
|
||||
|
||||
|
||||
@@ -55,14 +55,20 @@ namespace MQTTnet.Serializer
|
||||
Write(value);
|
||||
}
|
||||
|
||||
public static void WriteRemainingLength(int length, BinaryWriter target)
|
||||
public static int EncodeRemainingLength(int length, MemoryStream stream)
|
||||
{
|
||||
if (length == 0)
|
||||
// write the encoded remaining length right aligned on the 4 byte buffer
|
||||
|
||||
if (length <= 0)
|
||||
{
|
||||
target.Write((byte)0);
|
||||
return;
|
||||
stream.Seek(3, SeekOrigin.Current);
|
||||
stream.WriteByte(0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
var buffer = new byte[4];
|
||||
var offset = 0;
|
||||
|
||||
// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
|
||||
var x = length;
|
||||
do
|
||||
@@ -74,8 +80,15 @@ namespace MQTTnet.Serializer
|
||||
encodedByte = encodedByte | 128;
|
||||
}
|
||||
|
||||
target.Write((byte)encodedByte);
|
||||
buffer[offset] = (byte)encodedByte;
|
||||
|
||||
offset++;
|
||||
} while (x > 0);
|
||||
|
||||
stream.Seek(4 - offset, SeekOrigin.Current);
|
||||
stream.Write(buffer, 0, offset);
|
||||
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,15 @@ using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
// TODO: Rename to "RegisteredClient"
|
||||
// TODO: Add IsConnected
|
||||
// TODO: Add interface
|
||||
|
||||
public class ConnectedMqttClient
|
||||
{
|
||||
public string ClientId { get; set; }
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; }
|
||||
public MqttProtocolVersion? ProtocolVersion { get; set; }
|
||||
|
||||
public TimeSpan LastPacketReceived { get; set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public enum MqttClientDisconnectType
|
||||
{
|
||||
Clean,
|
||||
NotClean
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,12 @@ namespace MQTTnet.Server
|
||||
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch();
|
||||
|
||||
private readonly string _clientId;
|
||||
private readonly Func<Task> _timeoutCallback;
|
||||
private readonly Action _timeoutCallback;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
private Task _workerTask;
|
||||
|
||||
public MqttClientKeepAliveMonitor(string clientId, Func<Task> timeoutCallback, IMqttNetLogger logger)
|
||||
public MqttClientKeepAliveMonitor(string clientId, Action timeoutCallback, IMqttNetLogger logger)
|
||||
{
|
||||
_clientId = clientId;
|
||||
_timeoutCallback = timeoutCallback;
|
||||
@@ -61,10 +61,7 @@ namespace MQTTnet.Server
|
||||
{
|
||||
_logger.Warning<MqttClientSession>("Client '{0}': Did not receive any packet or keep alive signal.", _clientId);
|
||||
|
||||
if (_timeoutCallback != null)
|
||||
{
|
||||
await _timeoutCallback().ConfigureAwait(false);
|
||||
}
|
||||
_timeoutCallback?.Invoke();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace MQTTnet.Server
|
||||
public sealed class MqttClientPendingMessagesQueue : IDisposable
|
||||
{
|
||||
private readonly ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>();
|
||||
private readonly SemaphoreSlim _queueWaitSemaphore = new SemaphoreSlim(0);
|
||||
private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent();
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly MqttClientSession _clientSession;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
@@ -66,7 +66,7 @@ namespace MQTTnet.Server
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
|
||||
_queue.Enqueue(packet);
|
||||
_queueWaitSemaphore.Release();
|
||||
_queueAutoResetEvent.Set();
|
||||
|
||||
_logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _clientSession.ClientId);
|
||||
}
|
||||
@@ -94,7 +94,7 @@ namespace MQTTnet.Server
|
||||
MqttBasePacket packet = null;
|
||||
try
|
||||
{
|
||||
await _queueWaitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
await _queueAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (!_queue.TryDequeue(out packet))
|
||||
{
|
||||
throw new InvalidOperationException(); // should not happen
|
||||
@@ -105,7 +105,7 @@ namespace MQTTnet.Server
|
||||
return;
|
||||
}
|
||||
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { packet }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
|
||||
}
|
||||
@@ -132,21 +132,21 @@ namespace MQTTnet.Server
|
||||
if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
publishPacket.Dup = true;
|
||||
_queue.Enqueue(packet);
|
||||
_queueWaitSemaphore.Release();
|
||||
|
||||
Enqueue(publishPacket);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await _clientSession.StopAsync().ConfigureAwait(false);
|
||||
_clientSession.Stop(MqttClientDisconnectType.NotClean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_queueWaitSemaphore?.Dispose();
|
||||
_queueAutoResetEvent?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ namespace MQTTnet.Server
|
||||
public sealed class MqttClientSession : IDisposable
|
||||
{
|
||||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly MqttClientSessionsManager _sessionsManager;
|
||||
|
||||
private IMqttChannelAdapter _adapter;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private MqttApplicationMessage _willMessage;
|
||||
private bool _wasCleanDisconnect;
|
||||
@@ -28,22 +28,22 @@ namespace MQTTnet.Server
|
||||
public MqttClientSession(
|
||||
string clientId,
|
||||
IMqttServerOptions options,
|
||||
MqttClientSessionsManager sessionsManager,
|
||||
MqttRetainedMessagesManager retainedMessagesManager,
|
||||
IMqttNetLogger logger)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_sessionsManager = sessionsManager;
|
||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
ClientId = clientId;
|
||||
|
||||
KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeoutAsync, _logger);
|
||||
SubscriptionsManager = new MqttClientSubscriptionsManager(_options, clientId);
|
||||
KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeout, _logger);
|
||||
SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server);
|
||||
PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
|
||||
}
|
||||
|
||||
public Func<MqttClientSession, MqttApplicationMessage, Task> ApplicationMessageReceivedCallback { get; set; }
|
||||
|
||||
public MqttClientSubscriptionsManager SubscriptionsManager { get; }
|
||||
|
||||
public MqttClientPendingMessagesQueue PendingMessagesQueue { get; }
|
||||
@@ -52,9 +52,9 @@ namespace MQTTnet.Server
|
||||
|
||||
public string ClientId { get; }
|
||||
|
||||
public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion;
|
||||
public MqttProtocolVersion? ProtocolVersion { get; private set; }
|
||||
|
||||
public bool IsConnected => _adapter != null;
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public async Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
|
||||
{
|
||||
@@ -63,34 +63,45 @@ namespace MQTTnet.Server
|
||||
|
||||
try
|
||||
{
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
_wasCleanDisconnect = false;
|
||||
_willMessage = connectPacket.WillMessage;
|
||||
_adapter = adapter;
|
||||
_cancellationTokenSource = cancellationTokenSource;
|
||||
|
||||
PendingMessagesQueue.Start(adapter, cancellationTokenSource.Token);
|
||||
KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, cancellationTokenSource.Token);
|
||||
IsConnected = true;
|
||||
ProtocolVersion = adapter.PacketSerializer.ProtocolVersion;
|
||||
|
||||
await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
PendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token);
|
||||
KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token);
|
||||
|
||||
await ReceivePacketsAsync(adapter, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
_logger.Warning<MqttClientSession>(exception,
|
||||
"Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
_logger.Error<MqttClientSession>(exception,
|
||||
"Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ProtocolVersion = null;
|
||||
IsConnected = false;
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
|
||||
return _wasCleanDisconnect;
|
||||
}
|
||||
|
||||
public async Task StopAsync(bool wasCleanDisconnect = false)
|
||||
public void Stop(MqttClientDisconnectType type)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -99,37 +110,31 @@ namespace MQTTnet.Server
|
||||
return;
|
||||
}
|
||||
|
||||
_wasCleanDisconnect = wasCleanDisconnect;
|
||||
_wasCleanDisconnect = type == MqttClientDisconnectType.Clean;
|
||||
|
||||
_cancellationTokenSource?.Cancel(false);
|
||||
|
||||
PendingMessagesQueue.WaitForCompletion();
|
||||
KeepAliveMonitor.WaitForCompletion();
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
_adapter = null;
|
||||
|
||||
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var willMessage = _willMessage;
|
||||
_willMessage = null; // clear willmessage so it is send just once
|
||||
|
||||
if (willMessage != null && !wasCleanDisconnect)
|
||||
if (willMessage != null && !_wasCleanDisconnect)
|
||||
{
|
||||
await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false);
|
||||
_sessionsManager.StartDispatchApplicationMessage(this, willMessage);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage);
|
||||
var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage).ConfigureAwait(false);
|
||||
if (!result.IsSubscribed)
|
||||
{
|
||||
return;
|
||||
@@ -153,10 +158,10 @@ namespace MQTTnet.Server
|
||||
{
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
await SubscriptionsManager.SubscribeAsync(new MqttSubscribePacket
|
||||
SubscriptionsManager.Subscribe(new MqttSubscribePacket
|
||||
{
|
||||
TopicFilters = topicFilters
|
||||
}).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false);
|
||||
}
|
||||
@@ -165,26 +170,26 @@ namespace MQTTnet.Server
|
||||
{
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
return SubscriptionsManager.UnsubscribeAsync(new MqttUnsubscribePacket
|
||||
SubscriptionsManager.Unsubscribe(new MqttUnsubscribePacket
|
||||
{
|
||||
TopicFilters = topicFilters
|
||||
});
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ApplicationMessageReceivedCallback = null;
|
||||
|
||||
SubscriptionsManager?.Dispose();
|
||||
PendingMessagesQueue?.Dispose();
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
}
|
||||
|
||||
private Task StopDueToKeepAliveTimeoutAsync()
|
||||
private void StopDueToKeepAliveTimeout()
|
||||
{
|
||||
_logger.Info<MqttClientSession>("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId);
|
||||
return StopAsync();
|
||||
Stop(MqttClientDisconnectType.NotClean);
|
||||
}
|
||||
|
||||
private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||
@@ -201,15 +206,18 @@ namespace MQTTnet.Server
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
await StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
await StopAsync().ConfigureAwait(false);
|
||||
if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
}
|
||||
|
||||
Stop(MqttClientDisconnectType.NotClean);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +230,7 @@ namespace MQTTnet.Server
|
||||
|
||||
if (packet is MqttPingReqPacket)
|
||||
{
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttPingRespPacket() });
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { new MqttPingRespPacket() }, cancellationToken);
|
||||
}
|
||||
|
||||
if (packet is MqttPubRelPacket pubRelPacket)
|
||||
@@ -237,7 +245,7 @@ namespace MQTTnet.Server
|
||||
PacketIdentifier = pubRecPacket.PacketIdentifier
|
||||
};
|
||||
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { responsePacket });
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { responsePacket }, cancellationToken);
|
||||
}
|
||||
|
||||
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
|
||||
@@ -258,35 +266,20 @@ namespace MQTTnet.Server
|
||||
|
||||
if (packet is MqttDisconnectPacket)
|
||||
{
|
||||
return StopAsync(true);
|
||||
Stop(MqttClientDisconnectType.Clean);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
if (packet is MqttConnectPacket)
|
||||
{
|
||||
return StopAsync();
|
||||
Stop(MqttClientDisconnectType.NotClean);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
_logger.Warning<MqttClientSession>("Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
|
||||
return StopAsync();
|
||||
}
|
||||
Stop(MqttClientDisconnectType.NotClean);
|
||||
|
||||
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { subscribeResult.ResponsePacket }).ConfigureAwait(false);
|
||||
|
||||
if (subscribeResult.CloseConnection)
|
||||
{
|
||||
await StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { unsubscribeResult });
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters)
|
||||
@@ -298,6 +291,26 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var subscribeResult = SubscriptionsManager.Subscribe(subscribePacket);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { subscribeResult.ResponsePacket }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (subscribeResult.CloseConnection)
|
||||
{
|
||||
Stop(MqttClientDisconnectType.NotClean);
|
||||
return;
|
||||
}
|
||||
|
||||
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var unsubscribeResult = SubscriptionsManager.Unsubscribe(unsubscribePacket);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { unsubscribeResult }, cancellationToken);
|
||||
}
|
||||
|
||||
private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var applicationMessage = publishPacket.ToApplicationMessage();
|
||||
@@ -306,7 +319,8 @@ namespace MQTTnet.Server
|
||||
{
|
||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||
{
|
||||
return ApplicationMessageReceivedCallback?.Invoke(this, applicationMessage);
|
||||
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||
{
|
||||
@@ -325,25 +339,25 @@ namespace MQTTnet.Server
|
||||
|
||||
private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
|
||||
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
|
||||
|
||||
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
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 ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
|
||||
_sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
|
||||
|
||||
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier };
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response });
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,38 +8,34 @@ using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
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 SemaphoreSlim _sessionsLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger)
|
||||
public MqttClientSessionsManager(IMqttServerOptions options, MqttServer server, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
Server = server ?? throw new ArgumentNullException(nameof(server));
|
||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
||||
}
|
||||
|
||||
public Action<ConnectedMqttClient> ClientConnectedCallback { get; set; }
|
||||
public Action<ConnectedMqttClient, bool> 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 MqttServer Server { get; }
|
||||
|
||||
public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
|
||||
{
|
||||
var clientId = string.Empty;
|
||||
var wasCleanDisconnect = false;
|
||||
MqttClientSession clientSession = null;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken)
|
||||
@@ -57,30 +53,30 @@ namespace MQTTnet.Server
|
||||
var connectReturnCode = ValidateConnection(connectPacket);
|
||||
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
||||
{
|
||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
|
||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[]
|
||||
{
|
||||
new MqttConnAckPacket
|
||||
{
|
||||
ConnectReturnCode = connectReturnCode
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false);
|
||||
var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false);
|
||||
clientSession = result.Session;
|
||||
|
||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[]
|
||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[]
|
||||
{
|
||||
new MqttConnAckPacket
|
||||
{
|
||||
ConnectReturnCode = connectReturnCode,
|
||||
IsSessionPresent = result.IsExistingSession
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
ClientConnectedCallback?.Invoke(new ConnectedMqttClient
|
||||
Server.OnClientConnected(new ConnectedMqttClient
|
||||
{
|
||||
ClientId = clientId,
|
||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
|
||||
@@ -99,15 +95,15 @@ namespace MQTTnet.Server
|
||||
{
|
||||
try
|
||||
{
|
||||
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
|
||||
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
|
||||
clientAdapter.Dispose();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSessionsManager>(exception, exception.Message);
|
||||
}
|
||||
|
||||
ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient
|
||||
|
||||
Server.OnClientDisconnected(new ConnectedMqttClient
|
||||
{
|
||||
ClientId = clientId,
|
||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion,
|
||||
@@ -119,31 +115,31 @@ namespace MQTTnet.Server
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
foreach (var session in _sessions)
|
||||
{
|
||||
await session.Value.StopAsync().ConfigureAwait(false);
|
||||
session.Value.Stop(MqttClientDisconnectType.NotClean);
|
||||
}
|
||||
|
||||
_sessions.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
_sessionsLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
|
||||
{
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
|
||||
{
|
||||
ClientId = s.Value.ClientId,
|
||||
ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311,
|
||||
ProtocolVersion = s.Value.ProtocolVersion,
|
||||
LastPacketReceived = s.Value.KeepAliveMonitor.LastPacketReceived,
|
||||
LastNonKeepAlivePacketReceived = s.Value.KeepAliveMonitor.LastNonKeepAlivePacketReceived,
|
||||
PendingApplicationMessages = s.Value.PendingMessagesQueue.Count
|
||||
@@ -151,49 +147,13 @@ namespace MQTTnet.Server
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
_sessionsLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||
public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage);
|
||||
if (interceptorContext.CloseConnection)
|
||||
{
|
||||
await senderClientSession.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (applicationMessage.Retain)
|
||||
{
|
||||
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
ApplicationMessageReceivedCallback?.Invoke(senderClientSession?.ClientId, applicationMessage);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSessionsManager>(exception, "Error while processing application message");
|
||||
}
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
foreach (var clientSession in _sessions.Values)
|
||||
{
|
||||
await clientSession.EnqueueApplicationMessageAsync(applicationMessage);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage));
|
||||
}
|
||||
|
||||
public async Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
|
||||
@@ -201,19 +161,19 @@ namespace MQTTnet.Server
|
||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (!_sessions.TryGetValue(clientId, out var session))
|
||||
{
|
||||
throw new InvalidOperationException($"Client session {clientId} is unknown.");
|
||||
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||
}
|
||||
|
||||
await session.SubscribeAsync(topicFilters);
|
||||
await session.SubscribeAsync(topicFilters).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
_sessionsLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,36 +182,25 @@ namespace MQTTnet.Server
|
||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (!_sessions.TryGetValue(clientId, out var session))
|
||||
{
|
||||
throw new InvalidOperationException($"Client session {clientId} is unknown.");
|
||||
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||
}
|
||||
|
||||
await session.UnsubscribeAsync(topicFilters);
|
||||
await session.UnsubscribeAsync(topicFilters).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
_sessionsLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||
public void Dispose()
|
||||
{
|
||||
var interceptorContext = new MqttApplicationMessageInterceptorContext(
|
||||
senderClientSession?.ClientId,
|
||||
applicationMessage);
|
||||
|
||||
var interceptor = _options.ApplicationMessageInterceptor;
|
||||
if (interceptor == null)
|
||||
{
|
||||
return interceptorContext;
|
||||
}
|
||||
|
||||
interceptor(interceptorContext);
|
||||
return interceptorContext;
|
||||
_sessionsLock?.Dispose();
|
||||
}
|
||||
|
||||
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
|
||||
@@ -271,9 +220,9 @@ namespace MQTTnet.Server
|
||||
return context.ReturnCode;
|
||||
}
|
||||
|
||||
private async Task<GetOrCreateClientSessionResult> GetOrCreateClientSessionAsync(MqttConnectPacket connectPacket)
|
||||
private async Task<GetOrCreateClientSessionResult> PrepareClientSessionAsync(MqttConnectPacket connectPacket)
|
||||
{
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
|
||||
@@ -283,7 +232,7 @@ namespace MQTTnet.Server
|
||||
{
|
||||
_sessions.Remove(connectPacket.ClientId);
|
||||
|
||||
await clientSession.StopAsync().ConfigureAwait(false);
|
||||
clientSession.Stop(MqttClientDisconnectType.Clean);
|
||||
clientSession.Dispose();
|
||||
clientSession = null;
|
||||
|
||||
@@ -300,14 +249,7 @@ namespace MQTTnet.Server
|
||||
{
|
||||
isExistingSession = false;
|
||||
|
||||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, _retainedMessagesManager, _logger)
|
||||
{
|
||||
ApplicationMessageReceivedCallback = DispatchApplicationMessageAsync
|
||||
};
|
||||
|
||||
clientSession.SubscriptionsManager.TopicSubscribedCallback = ClientSubscribedTopicCallback;
|
||||
clientSession.SubscriptionsManager.TopicUnsubscribedCallback = ClientUnsubscribedTopicCallback;
|
||||
|
||||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _retainedMessagesManager, _logger);
|
||||
_sessions[connectPacket.ClientId] = clientSession;
|
||||
|
||||
_logger.Verbose<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId);
|
||||
@@ -317,19 +259,65 @@ namespace MQTTnet.Server
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
_sessionsLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
private async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
ClientConnectedCallback = null;
|
||||
ClientDisconnectedCallback = null;
|
||||
ClientSubscribedTopicCallback = null;
|
||||
ClientUnsubscribedTopicCallback = null;
|
||||
ApplicationMessageReceivedCallback = null;
|
||||
try
|
||||
{
|
||||
var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage);
|
||||
if (interceptorContext.CloseConnection)
|
||||
{
|
||||
senderClientSession.Stop(MqttClientDisconnectType.NotClean);
|
||||
}
|
||||
|
||||
_semaphore?.Dispose();
|
||||
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (applicationMessage.Retain)
|
||||
{
|
||||
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSessionsManager>(exception, "Error while processing application message");
|
||||
}
|
||||
|
||||
await _sessionsLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
foreach (var clientSession in _sessions.Values)
|
||||
{
|
||||
await clientSession.EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
var interceptorContext = new MqttApplicationMessageInterceptorContext(
|
||||
senderClientSession?.ClientId,
|
||||
applicationMessage);
|
||||
|
||||
var interceptor = _options.ApplicationMessageInterceptor;
|
||||
if (interceptor == null)
|
||||
{
|
||||
return interceptorContext;
|
||||
}
|
||||
|
||||
interceptor(interceptorContext);
|
||||
return interceptorContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -10,21 +11,20 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientSubscriptionsManager : IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, MqttQualityOfServiceLevel> _subscriptions = new ConcurrentDictionary<string, MqttQualityOfServiceLevel>();
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
||||
private readonly IMqttServerOptions _options;
|
||||
private readonly MqttServer _server;
|
||||
private readonly string _clientId;
|
||||
|
||||
public MqttClientSubscriptionsManager(IMqttServerOptions options, string clientId)
|
||||
public MqttClientSubscriptionsManager(string clientId, IMqttServerOptions options, MqttServer server)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_server = server;
|
||||
}
|
||||
|
||||
public Action<string, TopicFilter> TopicSubscribedCallback { get; set; }
|
||||
public Action<string, string> TopicUnsubscribedCallback { get; set; }
|
||||
|
||||
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttSubscribePacket subscribePacket)
|
||||
public MqttClientSubscribeResult Subscribe(MqttSubscribePacket subscribePacket)
|
||||
{
|
||||
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
|
||||
|
||||
@@ -38,57 +38,41 @@ namespace MQTTnet.Server
|
||||
CloseConnection = false
|
||||
};
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
foreach (var topicFilter in subscribePacket.TopicFilters)
|
||||
{
|
||||
foreach (var topicFilter in subscribePacket.TopicFilters)
|
||||
var interceptorContext = InterceptSubscribe(topicFilter);
|
||||
if (!interceptorContext.AcceptSubscription)
|
||||
{
|
||||
var interceptorContext = InterceptSubscribe(topicFilter);
|
||||
if (!interceptorContext.AcceptSubscription)
|
||||
{
|
||||
result.ResponsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ResponsePacket.SubscribeReturnCodes.Add(ConvertToMaximumQoS(topicFilter.QualityOfServiceLevel));
|
||||
}
|
||||
|
||||
if (interceptorContext.CloseConnection)
|
||||
{
|
||||
result.CloseConnection = true;
|
||||
}
|
||||
|
||||
if (interceptorContext.AcceptSubscription)
|
||||
{
|
||||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
|
||||
TopicSubscribedCallback?.Invoke(_clientId, topicFilter);
|
||||
}
|
||||
result.ResponsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ResponsePacket.SubscribeReturnCodes.Add(ConvertToMaximumQoS(topicFilter.QualityOfServiceLevel));
|
||||
}
|
||||
|
||||
if (interceptorContext.CloseConnection)
|
||||
{
|
||||
result.CloseConnection = true;
|
||||
}
|
||||
|
||||
if (interceptorContext.AcceptSubscription)
|
||||
{
|
||||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
|
||||
_server.OnClientSubscribedTopic(_clientId, topicFilter);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<MqttUnsubAckPacket> UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket)
|
||||
public MqttUnsubAckPacket Unsubscribe(MqttUnsubscribePacket unsubscribePacket)
|
||||
{
|
||||
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
|
||||
|
||||
await _semaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
||||
{
|
||||
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
||||
{
|
||||
_subscriptions.Remove(topicFilter);
|
||||
TopicUnsubscribedCallback?.Invoke(_clientId, topicFilter);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
_subscriptions.TryRemove(topicFilter, out _);
|
||||
_server.OnClientUnsubscribedTopic(_clientId, topicFilter);
|
||||
}
|
||||
|
||||
return new MqttUnsubAckPacket
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace MQTTnet.Server
|
||||
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
|
||||
}
|
||||
|
||||
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||
public Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||
{
|
||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||
|
||||
@@ -65,8 +65,10 @@ namespace MQTTnet.Server
|
||||
|
||||
foreach (var applicationMessage in applicationMessages)
|
||||
{
|
||||
await _clientSessionsManager.DispatchApplicationMessageAsync(null, applicationMessage);
|
||||
_clientSessionsManager.StartDispatchApplicationMessage(null, applicationMessage);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public async Task StartAsync(IMqttServerOptions options)
|
||||
@@ -80,14 +82,7 @@ namespace MQTTnet.Server
|
||||
_retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger);
|
||||
await _retainedMessagesManager.LoadMessagesAsync();
|
||||
|
||||
_clientSessionsManager = new MqttClientSessionsManager(Options, _retainedMessagesManager, _logger)
|
||||
{
|
||||
ClientConnectedCallback = OnClientConnected,
|
||||
ClientDisconnectedCallback = OnClientDisconnected,
|
||||
ClientSubscribedTopicCallback = OnClientSubscribedTopic,
|
||||
ClientUnsubscribedTopicCallback = OnClientUnsubscribedTopic,
|
||||
ApplicationMessageReceivedCallback = OnApplicationMessageReceived
|
||||
};
|
||||
_clientSessionsManager = new MqttClientSessionsManager(Options, this, _retainedMessagesManager, _logger);
|
||||
|
||||
foreach (var adapter in _adapters)
|
||||
{
|
||||
@@ -132,29 +127,29 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClientConnected(ConnectedMqttClient client)
|
||||
internal void OnClientConnected(ConnectedMqttClient client)
|
||||
{
|
||||
_logger.Info<MqttServer>("Client '{0}': Connected.", client.ClientId);
|
||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client));
|
||||
}
|
||||
|
||||
private void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect)
|
||||
internal void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect)
|
||||
{
|
||||
_logger.Info<MqttServer>("Client '{0}': Disconnected (clean={1}).", client.ClientId, wasCleanDisconnect);
|
||||
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client, wasCleanDisconnect));
|
||||
}
|
||||
|
||||
private void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter)
|
||||
internal void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter)
|
||||
{
|
||||
ClientSubscribedTopic?.Invoke(this, new MqttClientSubscribedTopicEventArgs(clientId, topicFilter));
|
||||
}
|
||||
|
||||
private void OnClientUnsubscribedTopic(string clientId, string topicFilter)
|
||||
internal void OnClientUnsubscribedTopic(string clientId, string topicFilter)
|
||||
{
|
||||
ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter));
|
||||
}
|
||||
|
||||
private void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage)
|
||||
internal void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage));
|
||||
}
|
||||
|
||||
@@ -19,12 +19,17 @@ namespace MQTTnet.Server
|
||||
var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None);
|
||||
var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None);
|
||||
|
||||
// # > In either case it MUST be the last character specified in the Topic Filter [MQTT-4.7.1-2].
|
||||
for (var i = 0; i < fragmentsFilter.Length; i++)
|
||||
{
|
||||
switch (fragmentsFilter[i])
|
||||
if (fragmentsFilter[i] == "+")
|
||||
{
|
||||
case "+": continue;
|
||||
case "#" when i == fragmentsFilter.Length - 1: return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fragmentsFilter[i] == "#")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (i >= fragmentsTopic.Length)
|
||||
@@ -38,7 +43,7 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
return fragmentsTopic.Length <= fragmentsFilter.Length;
|
||||
return fragmentsTopic.Length == fragmentsFilter.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<AssemblyName>MQTTnet</AssemblyName>
|
||||
<RootNamespace>MQTTnet</RootNamespace>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
<DebugType>Full</DebugType>
|
||||
<AssemblyVersion>0.0.0.0</AssemblyVersion>
|
||||
<FileVersion>0.0.0.0</FileVersion>
|
||||
<Version>0.0.0.0</Version>
|
||||
@@ -19,7 +18,7 @@
|
||||
<DelaySign>false</DelaySign>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'">
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
@@ -32,6 +31,10 @@
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DebugType>Full</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
|
||||
@@ -43,17 +46,17 @@
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.2" />
|
||||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.2" />
|
||||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'">
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="5.4.1" />
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net452'">
|
||||
|
||||
Reference in New Issue
Block a user