Merge pull request #1 from chkr1011/master

Merging with mainline.
This commit is contained in:
Dvergatal
2020-10-30 11:16:09 +01:00
committed by GitHub
7 changed files with 149 additions and 93 deletions
+1 -4
View File
@@ -12,10 +12,7 @@
<requireLicenseAcceptance>true</requireLicenseAcceptance> <requireLicenseAcceptance>true</requireLicenseAcceptance>
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</description> <description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</description>
<releaseNotes> <releaseNotes>
* [Client] Fixed wrong value for "ClientWasConnected" in "MqttClientDisconnectedEventArgs" #976 (thanks to @dbeinder). * [Server] ..
* [Client] Added direct support for Amazon AWS connections (requires .NET Core 3.1) (thanks to @henning-krause).
* [ManagedClient] Exposed the internal MQTT client.
* [Server] Added client message queue interceptor for QoS level (tahnks to @msallin).
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2020</copyright> <copyright>Copyright Christian Kratky 2016-2020</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</tags>
+20 -12
View File
@@ -245,16 +245,22 @@ namespace MQTTnet.Client
void Cleanup() void Cleanup()
{ {
_backgroundCancellationTokenSource?.Cancel(false); try
_backgroundCancellationTokenSource?.Dispose(); {
_backgroundCancellationTokenSource = null; _backgroundCancellationTokenSource?.Cancel(false);
}
finally
{
_backgroundCancellationTokenSource?.Dispose();
_backgroundCancellationTokenSource = null;
_publishPacketReceiverQueue?.Dispose(); _publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = null; _publishPacketReceiverQueue = null;
_adapter?.Dispose(); _adapter?.Dispose();
}
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
@@ -387,7 +393,7 @@ namespace MQTTnet.Client
async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
ushort identifier = 0; ushort identifier = 0;
if (requestPacket is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue) if (requestPacket is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue)
{ {
@@ -436,7 +442,7 @@ namespace MQTTnet.Client
{ {
// Values described here: [MQTT-3.1.2-24]. // Values described here: [MQTT-3.1.2-24].
var waitTime = keepAlivePeriod - _sendTracker.Elapsed; var waitTime = keepAlivePeriod - _sendTracker.Elapsed;
if (waitTime <= TimeSpan.Zero) if (waitTime <= TimeSpan.Zero)
{ {
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket(), cancellationToken).ConfigureAwait(false); await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket(), cancellationToken).ConfigureAwait(false);
@@ -561,12 +567,15 @@ namespace MQTTnet.Client
{ {
await SendAsync(new MqttPingRespPacket(), cancellationToken).ConfigureAwait(false); await SendAsync(new MqttPingRespPacket(), cancellationToken).ConfigureAwait(false);
} }
else if (packet is MqttDisconnectPacket) else if (packet is MqttDisconnectPacket disconnectPacket)
{ {
// Also dispatch disconnect to waiting threads to generate a proper exception. // Also dispatch disconnect to waiting threads to generate a proper exception.
_packetDispatcher.Dispatch(packet); _packetDispatcher.Dispatch(packet);
await DisconnectAsync(null, cancellationToken).ConfigureAwait(false); await DisconnectAsync(new MqttClientDisconnectOptions()
{
ReasonCode = (MqttClientDisconnectReason)(disconnectPacket.ReasonCode ?? MqttDisconnectReasonCode.UnspecifiedError)
}, cancellationToken).ConfigureAwait(false);
} }
else if (packet is MqttAuthPacket authPacket) else if (packet is MqttAuthPacket authPacket)
{ {
@@ -628,7 +637,6 @@ namespace MQTTnet.Client
try try
{ {
var publishPacketDequeueResult = await _publishPacketReceiverQueue.TryDequeueAsync(cancellationToken); var publishPacketDequeueResult = await _publishPacketReceiverQueue.TryDequeueAsync(cancellationToken);
if (!publishPacketDequeueResult.IsSuccess) if (!publishPacketDequeueResult.IsSuccess)
{ {
return; return;
@@ -87,7 +87,7 @@ namespace MQTTnet.Implementations
ApplicationProtocols = _options.TlsOptions.ApplicationProtocols, ApplicationProtocols = _options.TlsOptions.ApplicationProtocols,
ClientCertificates = LoadCertificates(), ClientCertificates = LoadCertificates(),
EnabledSslProtocols = _options.TlsOptions.SslProtocol, EnabledSslProtocols = _options.TlsOptions.SslProtocol,
CertificateRevocationCheckMode = _options.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.Online : X509RevocationMode.NoCheck, CertificateRevocationCheckMode = _options.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : X509RevocationMode.Online,
TargetHost = _options.Server TargetHost = _options.Server
}; };
+13 -8
View File
@@ -7,16 +7,15 @@ namespace MQTTnet.Internal
{ {
public sealed class AsyncQueue<TItem> : IDisposable public sealed class AsyncQueue<TItem> : IDisposable
{ {
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0); SemaphoreSlim _semaphore = new SemaphoreSlim(0);
ConcurrentQueue<TItem> _queue = new ConcurrentQueue<TItem>();
private ConcurrentQueue<TItem> _queue = new ConcurrentQueue<TItem>();
public int Count => _queue.Count; public int Count => _queue.Count;
public void Enqueue(TItem item) public void Enqueue(TItem item)
{ {
_queue.Enqueue(item); _queue.Enqueue(item);
_semaphore.Release(); _semaphore?.Release();
} }
public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken) public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken)
@@ -25,12 +24,17 @@ namespace MQTTnet.Internal
{ {
try try
{ {
if (_semaphore == null)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
return new AsyncQueueDequeueResult<TItem>(false, default(TItem)); return new AsyncQueueDequeueResult<TItem>(false, default);
} }
if (_queue.TryDequeue(out var item)) if (_queue.TryDequeue(out var item))
@@ -39,7 +43,7 @@ namespace MQTTnet.Internal
} }
} }
return new AsyncQueueDequeueResult<TItem>(false, default(TItem)); return new AsyncQueueDequeueResult<TItem>(false, default);
} }
public AsyncQueueDequeueResult<TItem> TryDequeue() public AsyncQueueDequeueResult<TItem> TryDequeue()
@@ -49,7 +53,7 @@ namespace MQTTnet.Internal
return new AsyncQueueDequeueResult<TItem>(true, item); return new AsyncQueueDequeueResult<TItem>(true, item);
} }
return new AsyncQueueDequeueResult<TItem>(false, default(TItem)); return new AsyncQueueDequeueResult<TItem>(false, default);
} }
public void Clear() public void Clear()
@@ -60,6 +64,7 @@ namespace MQTTnet.Internal
public void Dispose() public void Dispose()
{ {
_semaphore?.Dispose(); _semaphore?.Dispose();
_semaphore = null;
} }
} }
} }
+28 -19
View File
@@ -23,8 +23,6 @@ namespace MQTTnet.Server
readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource(); readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource();
readonly IMqttRetainedMessagesManager _retainedMessagesManager; readonly IMqttRetainedMessagesManager _retainedMessagesManager;
readonly Func<Task> _onStart;
readonly Func<MqttClientDisconnectType, Task> _onStop;
readonly MqttClientKeepAliveMonitor _keepAliveMonitor; readonly MqttClientKeepAliveMonitor _keepAliveMonitor;
readonly MqttClientSessionsManager _sessionsManager; readonly MqttClientSessionsManager _sessionsManager;
@@ -32,6 +30,7 @@ namespace MQTTnet.Server
readonly IMqttServerOptions _serverOptions; readonly IMqttServerOptions _serverOptions;
readonly IMqttChannelAdapter _channelAdapter; readonly IMqttChannelAdapter _channelAdapter;
readonly MqttConnectionValidatorContext _connectionValidatorContext;
readonly IMqttDataConverter _dataConverter; readonly IMqttDataConverter _dataConverter;
readonly string _endpoint; readonly string _endpoint;
readonly DateTime _connectedTimestamp; readonly DateTime _connectedTimestamp;
@@ -47,25 +46,22 @@ namespace MQTTnet.Server
volatile bool _isTakeover; volatile bool _isTakeover;
public MqttClientConnection( public MqttClientConnection(MqttConnectPacket connectPacket,
MqttConnectPacket connectPacket,
IMqttChannelAdapter channelAdapter, IMqttChannelAdapter channelAdapter,
MqttClientSession session, MqttClientSession session,
MqttConnectionValidatorContext connectionValidatorContext,
IMqttServerOptions serverOptions, IMqttServerOptions serverOptions,
MqttClientSessionsManager sessionsManager, MqttClientSessionsManager sessionsManager,
IMqttRetainedMessagesManager retainedMessagesManager, IMqttRetainedMessagesManager retainedMessagesManager,
Func<Task> onStart,
Func<MqttClientDisconnectType, Task> onStop,
IMqttNetLogger logger) IMqttNetLogger logger)
{ {
Session = session ?? throw new ArgumentNullException(nameof(session)); Session = session ?? throw new ArgumentNullException(nameof(session));
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
_onStart = onStart ?? throw new ArgumentNullException(nameof(onStart));
_onStop = onStop ?? throw new ArgumentNullException(nameof(onStop));
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter)); _channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
_connectionValidatorContext = connectionValidatorContext ?? throw new ArgumentNullException(nameof(connectionValidatorContext));
_dataConverter = _channelAdapter.PacketFormatterAdapter.DataConverter; _dataConverter = _channelAdapter.PacketFormatterAdapter.DataConverter;
_endpoint = _channelAdapter.Endpoint; _endpoint = _channelAdapter.Endpoint;
ConnectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); ConnectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
@@ -131,18 +127,17 @@ namespace MQTTnet.Server
_cancellationToken.Dispose(); _cancellationToken.Dispose();
} }
public Task RunAsync(MqttConnectionValidatorContext connectionValidatorContext) public Task RunAsync()
{ {
_packageReceiverTask = RunInternalAsync(connectionValidatorContext); _packageReceiverTask = RunInternalAsync();
return _packageReceiverTask; return _packageReceiverTask;
} }
async Task RunInternalAsync(MqttConnectionValidatorContext connectionValidatorContext) async Task RunInternalAsync()
{ {
var disconnectType = MqttClientDisconnectType.NotClean; var disconnectType = MqttClientDisconnectType.NotClean;
try try
{ {
await _onStart();
_logger.Info("Client '{0}': Session started.", ClientId); _logger.Info("Client '{0}': Session started.", ClientId);
_channelAdapter.ReadingPacketStartedCallback = OnAdapterReadingPacketStarted; _channelAdapter.ReadingPacketStartedCallback = OnAdapterReadingPacketStarted;
@@ -155,9 +150,7 @@ namespace MQTTnet.Server
// TODO: Change to single thread in SessionManager. Or use SessionManager and stats from KeepAliveMonitor. // TODO: Change to single thread in SessionManager. Or use SessionManager and stats from KeepAliveMonitor.
_keepAliveMonitor.Start(ConnectPacket.KeepAlivePeriod, _cancellationToken.Token); _keepAliveMonitor.Start(ConnectPacket.KeepAlivePeriod, _cancellationToken.Token);
await SendAsync( await SendAsync(_channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(_connectionValidatorContext)).ConfigureAwait(false);
_channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext)
).ConfigureAwait(false);
Session.IsCleanSession = false; Session.IsCleanSession = false;
@@ -262,17 +255,33 @@ namespace MQTTnet.Server
_channelAdapter.ReadingPacketStartedCallback = null; _channelAdapter.ReadingPacketStartedCallback = null;
_channelAdapter.ReadingPacketCompletedCallback = null; _channelAdapter.ReadingPacketCompletedCallback = null;
_logger.Info("Client '{0}': Connection stopped.", ClientId);
_packageReceiverTask = null; _packageReceiverTask = null;
if (_isTakeover)
{
try
{
// Don't use SendAsync here _cancellationToken is already cancelled.
await _channelAdapter.SendPacketAsync(new MqttDisconnectPacket
{
ReasonCode = MqttDisconnectReasonCode.SessionTakenOver
}, TimeSpan.Zero, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, "Client '{0}': Error while sending DISCONNECT packet after takeover.", ClientId);
}
}
_logger.Info("Client '{0}': Connection stopped.", ClientId);
try try
{ {
await _onStop(disconnectType); await _sessionsManager.CleanUpClient(ClientId, _channelAdapter, disconnectType);
} }
catch (Exception e) catch (Exception e)
{ {
_logger.Error(e, "client '{0}': Error while cleaning up", ClientId); _logger.Error(e, "Client '{0}': Error while cleaning up", ClientId);
} }
} }
} }
@@ -21,6 +21,7 @@ namespace MQTTnet.Server
readonly AsyncLock _createConnectionGate = new AsyncLock(); readonly AsyncLock _createConnectionGate = new AsyncLock();
readonly ConcurrentDictionary<string, MqttClientConnection> _connections = new ConcurrentDictionary<string, MqttClientConnection>(); readonly ConcurrentDictionary<string, MqttClientConnection> _connections = new ConcurrentDictionary<string, MqttClientConnection>();
readonly ConcurrentDictionary<string, MqttClientSession> _sessions = new ConcurrentDictionary<string, MqttClientSession>(); readonly ConcurrentDictionary<string, MqttClientSession> _sessions = new ConcurrentDictionary<string, MqttClientSession>();
readonly IDictionary<object, object> _serverSessionItems = new ConcurrentDictionary<object, object>(); readonly IDictionary<object, object> _serverSessionItems = new ConcurrentDictionary<object, object>();
readonly CancellationToken _cancellationToken; readonly CancellationToken _cancellationToken;
@@ -183,7 +184,6 @@ namespace MQTTnet.Server
} }
var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false); var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false);
if (!dequeueResult.IsSuccess) if (!dequeueResult.IsSuccess)
{ {
return; return;
@@ -258,8 +258,6 @@ namespace MQTTnet.Server
async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
{ {
string clientId = null;
try try
{ {
MqttConnectPacket connectPacket; MqttConnectPacket connectPacket;
@@ -291,17 +289,9 @@ namespace MQTTnet.Server
return; return;
} }
clientId = connectPacket.ClientId; var connection = await CreateClientConnectionAsync(connectPacket, connectionValidatorContext, channelAdapter).ConfigureAwait(false);
await _eventDispatcher.SafeNotifyClientConnectedAsync(connectPacket.ClientId).ConfigureAwait(false);
var connection = await CreateClientConnectionAsync( await connection.RunAsync().ConfigureAwait(false);
connectPacket,
connectionValidatorContext,
channelAdapter,
async () => await _eventDispatcher.SafeNotifyClientConnectedAsync(clientId).ConfigureAwait(false),
async disconnectType => await CleanUpClient(clientId, channelAdapter, disconnectType)
).ConfigureAwait(false);
await connection.RunAsync(connectionValidatorContext).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -312,15 +302,19 @@ namespace MQTTnet.Server
} }
} }
private async Task CleanUpClient(string clientId, IMqttChannelAdapter channelAdapter, MqttClientDisconnectType disconnectType) public async Task CleanUpClient(string clientId, IMqttChannelAdapter channelAdapter, MqttClientDisconnectType disconnectType)
{ {
if (clientId != null) if (clientId != null)
{ {
_connections.TryRemove(clientId, out _); // in case it is a takeover _connections already contains the new connection
if (disconnectType != MqttClientDisconnectType.Takeover)
if (!_options.EnablePersistentSessions)
{ {
await DeleteSessionAsync(clientId).ConfigureAwait(false); _connections.TryRemove(clientId, out _);
if (!_options.EnablePersistentSessions)
{
await DeleteSessionAsync(clientId).ConfigureAwait(false);
}
} }
} }
@@ -360,45 +354,52 @@ namespace MQTTnet.Server
return context; return context;
} }
async Task<MqttClientConnection> CreateClientConnectionAsync(MqttConnectPacket connectPacket, MqttConnectionValidatorContext connectionValidatorContext, IMqttChannelAdapter channelAdapter, Func<Task> onStart, Func<MqttClientDisconnectType, Task> onStop) async Task<MqttClientConnection> CreateClientConnectionAsync(MqttConnectPacket connectPacket, MqttConnectionValidatorContext connectionValidatorContext, IMqttChannelAdapter channelAdapter)
{ {
MqttClientConnection connection;
MqttClientConnection existingConnection = null;
using (await _createConnectionGate.WaitAsync(_cancellationToken).ConfigureAwait(false)) using (await _createConnectionGate.WaitAsync(_cancellationToken).ConfigureAwait(false))
{ {
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var session); var session = _sessions.AddOrUpdate(
connectPacket.ClientId,
var isConnectionPresent = _connections.TryGetValue(connectPacket.ClientId, out var existingConnection); key =>
if (isConnectionPresent)
{
await existingConnection.StopAsync(true).ConfigureAwait(false);
}
if (isSessionPresent)
{
if (connectPacket.CleanSession)
{ {
session = null; _logger.Verbose("Created a new session for client '{0}'.", key);
return new MqttClientSession(key, connectionValidatorContext.SessionItems, _eventDispatcher, _options, _retainedMessagesManager, _rootLogger);
_logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId); },
} (key, existingSession) =>
else
{ {
if (connectPacket.CleanSession)
{
_logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId);
return new MqttClientSession(key, connectionValidatorContext.SessionItems, _eventDispatcher, _options, _retainedMessagesManager, _rootLogger);
}
_logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId); _logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
} return existingSession;
} });
if (session == null) connection = new MqttClientConnection(connectPacket, channelAdapter, session, connectionValidatorContext, _options, this, _retainedMessagesManager, _rootLogger);
{
session = new MqttClientSession(connectPacket.ClientId, connectionValidatorContext.SessionItems, _eventDispatcher, _options, _retainedMessagesManager, _rootLogger);
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
}
var connection = new MqttClientConnection(connectPacket, channelAdapter, session, _options, this, _retainedMessagesManager, onStart, onStop, _rootLogger); _connections.AddOrUpdate(
connectPacket.ClientId,
_connections[connection.ClientId] = connection; key => connection,
_sessions[session.ClientId] = session; (key, tempExistingConnection) =>
{
return connection; existingConnection = tempExistingConnection;
return connection;
});
} }
// Disconnect the client outside of the lock so that new clients can still connect while a single
// one is being disconnected.
if (existingConnection != null)
{
await existingConnection.StopAsync(true).ConfigureAwait(false);
}
return connection;
} }
async Task<MqttApplicationMessageInterceptorContext> InterceptApplicationMessageAsync(MqttClientConnection senderConnection, MqttApplicationMessage applicationMessage) async Task<MqttApplicationMessageInterceptorContext> InterceptApplicationMessageAsync(MqttClientConnection senderConnection, MqttApplicationMessage applicationMessage)
+36
View File
@@ -1,5 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Client.Options;
using MQTTnet.Client.Subscribing; using MQTTnet.Client.Subscribing;
using MQTTnet.Server; using MQTTnet.Server;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
@@ -84,5 +85,40 @@ namespace MQTTnet.Tests
Assert.AreEqual(true, session.Items["can_subscribe_x"]); Assert.AreEqual(true, session.Items["can_subscribe_x"]);
} }
} }
[TestMethod]
public async Task Manage_Session_MaxParallel()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
testEnvironment.IgnoreClientLogErrors = true;
var serverOptions = new MqttServerOptionsBuilder();
await testEnvironment.StartServerAsync(serverOptions);
var options = new MqttClientOptionsBuilder()
.WithClientId("1")
;
var clients = await Task.WhenAll(Enumerable.Range(0, 10)
.Select(i => TryConnect(testEnvironment, options)));
var connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
Assert.AreEqual(1, connectedClients.Count);
}
}
private async Task<IMqttClient> TryConnect(TestEnvironment testEnvironment, MqttClientOptionsBuilder options)
{
try
{
return await testEnvironment.ConnectClientAsync(options);
}
catch (System.Exception)
{
return null;
}
}
} }
} }