public async Task RunAsync(MqttApplicationMessage willMessage, IMqttChannelAdapter adapter) { if (adapter == null) { throw new ArgumentNullException(nameof(adapter)); } try { var cancellationTokenSource = new CancellationTokenSource(); _willMessage = willMessage; _adapter = adapter; _cancellationTokenSource = cancellationTokenSource; _pendingMessagesQueue.Start(adapter, 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); } catch (Exception exception) { _logger.Error <MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); } }
private async Task DisconnectInternalAsync() { var clientWasConnected = IsConnected; IsConnected = false; var cts = _cancellationTokenSource; if (cts == null || cts.IsCancellationRequested) { return; } cts.Cancel(false); cts.Dispose(); _cancellationTokenSource = null; try { await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); _logger.Info <MqttClient>("Disconnected from adapter."); } catch (Exception exception) { _logger.Warning <MqttClient>(exception, "Error while disconnecting from adapter."); } finally { _logger.Info <MqttClient>("Disconnected."); Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected)); } }
private async Task DisconnectInternalAsync(Task sender, Exception exception) { await _disconnectLock.WaitAsync(); try { if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested) { return; } _cancellationTokenSource.Cancel(false); } catch (Exception adapterException) { _logger.Warning <MqttClient>(adapterException, "Error while disconnecting from adapter."); } finally { _disconnectLock.Release(); } var clientWasConnected = IsConnected; IsConnected = false; try { if (_packetReceiverTask != null && _packetReceiverTask != sender) { _packetReceiverTask.Wait(); } if (_keepAliveMessageSenderTask != null && _keepAliveMessageSenderTask != sender) { _keepAliveMessageSenderTask.Wait(); } if (_adapter != null) { await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); } _logger.Verbose <MqttClient>("Disconnected from adapter."); } catch (Exception adapterException) { _logger.Warning <MqttClient>(adapterException, "Error while disconnecting from adapter."); } finally { _adapter?.Dispose(); _adapter = null; _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; _logger.Info <MqttClient>("Disconnected."); Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception)); } }
async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) { string clientId = null; MqttConnectPacket connectPacket; try { try { var firstPacket = await channelAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); connectPacket = firstPacket as MqttConnectPacket; if (connectPacket == null) { _logger.Warning(null, "The first packet from client '{0}' was no 'CONNECT' packet [MQTT-3.1.0-1].", channelAdapter.Endpoint); return; } } catch (MqttCommunicationTimedOutException) { _logger.Warning(null, "Client '{0}' connected but did not sent a CONNECT packet.", channelAdapter.Endpoint); return; } var connectionValidatorContext = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false); if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success) { // Send failure response here without preparing a session. The result for a successful connect // will be sent from the session itself. var connAckPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext); await channelAdapter.SendPacketAsync(connAckPacket, _options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); return; } clientId = connectPacket.ClientId; var connection = await CreateClientConnectionAsync( 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 (Exception exception) { _logger.Error(exception, exception.Message); } }
private async Task SendNextQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) { MqttBasePacket packet = null; try { await _queueWaitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); if (!_queue.TryDequeue(out packet)) { throw new InvalidOperationException(); // should not happen } if (cancellationToken.IsCancellationRequested) { return; } await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false); _logger.Trace <MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); } catch (Exception exception) { if (exception is MqttCommunicationTimedOutException) { _logger.Warning <MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to timeout (ClientId: {0}).", _clientSession.ClientId); } else if (exception is MqttCommunicationException) { _logger.Warning <MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to communication exception (ClientId: {0}).", _clientSession.ClientId); } else if (exception is OperationCanceledException) { } else { _logger.Error <MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId); } if (packet is MqttPublishPacket publishPacket) { if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { publishPacket.Dup = true; _queue.Enqueue(packet); _queueWaitSemaphore.Release(); } } if (!cancellationToken.IsCancellationRequested) { await _clientSession.StopAsync().ConfigureAwait(false); } } }
private async Task MaintainConnectionAsync(CancellationToken cancellationToken) { try { while (!cancellationToken.IsCancellationRequested) { var connectionState = await ReconnectIfRequiredAsync().ConfigureAwait(false); if (connectionState == ReconnectionResult.NotConnected) { _publishingCancellationToken?.Cancel(false); _publishingCancellationToken = null; await Task.Delay(_options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false); continue; } if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed) { await PushSubscriptionsAsync(); _publishingCancellationToken = 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 PublishQueuedMessagesAsync(_publishingCancellationToken.Token), _publishingCancellationToken.Token).ConfigureAwait(false); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed continue; } if (connectionState == ReconnectionResult.StillConnected) { await Task.Delay(TimeSpan.FromSeconds(1), _connectionCancellationToken.Token).ConfigureAwait(false); } } } catch (OperationCanceledException) { } catch (MqttCommunicationException exception) { _logger.Warning <ManagedMqttClient>(exception, "Communication exception while maintaining connection."); } catch (Exception exception) { _logger.Error <ManagedMqttClient>(exception, "Unhandled exception while maintaining connection."); } finally { await _mqttClient.DisconnectAsync().ConfigureAwait(false); _logger.Info <ManagedMqttClient>("Stopped"); } }
async Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientAuthenticateResult authenticateResult) { var clientWasConnected = IsConnected; TryInitiateDisconnect(); IsConnected = false; try { if (_adapter != null) { _logger.Verbose("Disconnecting [Timeout={0}]", Options.CommunicationTimeout); await _adapter.DisconnectAsync(Options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false); } _logger.Verbose("Disconnected from adapter."); } catch (Exception adapterException) { _logger.Warning(adapterException, "Error while disconnecting from adapter."); } try { var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender); var publishPacketReceiverTask = WaitForTaskAsync(_publishPacketReceiverTask, sender); var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender); await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false); _publishPacketReceiverQueue.Dispose(); } catch (Exception e) { _logger.Warning(e, "Error while waiting for internal tasks."); } finally { Cleanup(); _cleanDisconnectInitiated = false; _logger.Info("Disconnected."); var disconnectedHandler = DisconnectedHandler; if (disconnectedHandler != null) { // This handler must be executed in a new thread because otherwise a dead lock may happen // when trying to reconnect in that handler etc. Task.Run(() => disconnectedHandler.HandleDisconnectedAsync(new MqttClientDisconnectedEventArgs(clientWasConnected, exception, authenticateResult))).Forget(_logger); } } }
public async Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) { if (connectPacket == null) { throw new ArgumentNullException(nameof(connectPacket)); } if (adapter == null) { throw new ArgumentNullException(nameof(adapter)); } try { var cancellationTokenSource = new CancellationTokenSource(); _willMessage = connectPacket.WillMessage; _adapter = adapter; _cancellationTokenSource = cancellationTokenSource; _pendingMessagesQueue.Start(adapter, cancellationTokenSource.Token); _lastPacketReceivedTracker.Restart(); _lastNonKeepAlivePacketReceivedTracker.Restart(); if (connectPacket.KeepAlivePeriod > 0) { StartCheckingKeepAliveTimeout(TimeSpan.FromSeconds(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); } catch (Exception exception) { _logger.Error <MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); } }
private async Task TryMaintainConnectionAsync(CancellationToken cancellationToken) { try { var connectionState = await ReconnectIfRequiredAsync().ConfigureAwait(false); if (connectionState == ReconnectionResult.NotConnected) { StopPublishing(); await Task.Delay(_options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false); return; } if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed) { await SynchronizeSubscriptionsAsync().ConfigureAwait(false); StartPublishing(); return; } if (connectionState == ReconnectionResult.StillConnected) { await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) { } catch (MqttCommunicationException exception) { _logger.Warning <ManagedMqttClient>(exception, "Communication exception while maintaining connection."); } catch (Exception exception) { _logger.Error <ManagedMqttClient>(exception, "Unhandled exception while maintaining connection."); } }
private async Task SendQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) { MqttBasePacket packet = null; try { packet = _queue.Take(cancellationToken); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packet).ConfigureAwait(false); _logger.Trace <MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _session.ClientId); } catch (Exception exception) { if (exception is MqttCommunicationTimedOutException) { _logger.Warning <MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to timeout (ClientId: {0}).", _session.ClientId); } else if (exception is MqttCommunicationException) { _logger.Warning <MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to communication exception (ClientId: {0}).", _session.ClientId); } else if (exception is OperationCanceledException) { } else { _logger.Error <MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed (ClientId: {0}).", _session.ClientId); } if (packet is MqttPublishPacket publishPacket) { if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { publishPacket.Dup = true; _queue.Add(packet, CancellationToken.None); } } await _session.StopAsync(); } }
public async Task <bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) { if (connectPacket == null) { throw new ArgumentNullException(nameof(connectPacket)); } if (adapter == null) { throw new ArgumentNullException(nameof(adapter)); } try { var cancellationTokenSource = new CancellationTokenSource(); _wasCleanDisconnect = false; _willMessage = connectPacket.WillMessage; _adapter = adapter; _cancellationTokenSource = cancellationTokenSource; 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); } catch (Exception exception) { _logger.Error <MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); } return(_wasCleanDisconnect); }
async void OnConnectionReceivedAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) { try { var clientHandler = ClientHandler; if (clientHandler != null) { X509Certificate2 clientCertificate = null; if (args.Socket.Control.ClientCertificate != null) { try { clientCertificate = new X509Certificate2(args.Socket.Control.ClientCertificate.GetCertificateBlob().ToArray()); } catch (Exception exception) { _logger.Warning(exception, "Unable to convert UWP certificate to X509Certificate2."); } } using (var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket, clientCertificate, _options), new MqttPacketFormatterAdapter(), _logger)) { await clientHandler(clientAdapter).ConfigureAwait(false); } } } catch (Exception exception) { if (exception is ObjectDisposedException) { // It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. return; } _logger.Error(exception, "Error while handling client connection."); } finally { try { args.Socket.Dispose(); } catch (Exception exception) { _logger.Error(exception, "Error while cleaning up client connection"); } } }
public bool Start(bool treatErrorsAsWarning, CancellationToken cancellationToken) { try { var boundIp = _options.BoundInterNetworkAddress; if (_addressFamily == AddressFamily.InterNetworkV6) { boundIp = _options.BoundInterNetworkV6Address; } _localEndPoint = new IPEndPoint(boundIp, _options.Port); _logger.Info($"Starting TCP listener for {_localEndPoint} TLS={_tlsCertificate != null}."); _socket = new CrossPlatformSocket(_addressFamily); // Usage of socket options is described here: https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.setsocketoption?view=netcore-2.2 if (_options.ReuseAddress) { _socket.ReuseAddress = true; } if (_options.NoDelay) { _socket.NoDelay = true; } _socket.Bind(_localEndPoint); _socket.Listen(_options.ConnectionBacklog); Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).Forget(_logger); return(true); } catch (Exception exception) { if (!treatErrorsAsWarning) { throw; } _logger.Warning(exception, "Error while creating listener socket for local end point '{0}'.", _localEndPoint); return(false); } }
public async Task <MqttBasePacket> WaitForPacketAsync(Type responseType, ushort?identifier, TimeSpan timeout) { var packetAwaiter = AddPacketAwaiter(responseType, identifier); try { 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); } }
async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken) { try { _lastPacketReceivedTracker.Restart(); while (!cancellationToken.IsCancellationRequested) { // Values described here: [MQTT-3.1.2-24]. // If the client sends 5 sec. the server will allow up to 7.5 seconds. // If the client sends 1 sec. the server will allow up to 1.5 seconds. if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds >= keepAlivePeriod * 1.5D) { _logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientId); await _keepAliveElapsedCallback().ConfigureAwait(false); return; } // The server checks the keep alive timeout every 50 % of the overall keep alive timeout // because the server allows 1.5 times the keep alive value. This means that a value of 5 allows // up to 7.5 seconds. With an interval of 2.5 (5 / 2) the 7.5 is also affected. Waiting the whole // keep alive time will hit at 10 instead of 7.5 (but only one time instead of two times). await Task.Delay(TimeSpan.FromSeconds(keepAlivePeriod * 0.5D), cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) { } catch (Exception exception) { _logger.Error(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId); } finally { _logger.Verbose("Client '{0}': Stopped checking keep alive timeout.", _clientId); } }
public async Task <MqttBasePacket> WaitForPacketAsync(MqttBasePacket request, Type responseType, TimeSpan timeout) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var packetAwaiter = AddPacketAwaiter(request, responseType); try { 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(request, responseType); } }
public async Task Stop(TimeSpan timeout = default) { if (timeout == default) { timeout = Timeout.InfiniteTimeSpan; } var calls = _waitingCalls.Values.Select(x => x.Task).Concat(_noIdCalls.Values).ToArray(); try { _logger.Info($"Stopping {GetType().Name}, waiting {calls.Length} calls"); await Task.Run(async() => await Task.WhenAll(calls), new CancellationTokenSource(timeout).Token); StopInternal(); _logger.Info($"{GetType().Name} has stopped."); } catch (TaskCanceledException ex) { _logger.Warning(ex, $"Stop {GetType().Name} timed out, trying to force stop ..."); ForceStop(); } }
private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken) { try { _lastPacketReceivedTracker.Restart(); _lastNonKeepAlivePacketReceivedTracker.Restart(); while (!cancellationToken.IsCancellationRequested) { // Values described here: [MQTT-3.1.2-24]. if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D) { _logger.Warning <MqttClientSession>("Client '{0}': Did not receive any packet or keep alive signal.", _clientId); if (_timeoutCallback != null) { await _timeoutCallback().ConfigureAwait(false); } return; } await Task.Delay(keepAlivePeriod, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) { } catch (Exception exception) { _logger.Error <MqttClientSession>(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId); } finally { _logger.Trace <MqttClientSession>("Client {0}: Stopped checking keep alive timeout.", _clientId); } }
async Task RunInternalAsync(MqttConnectionValidatorContext connectionValidatorContext) { var disconnectType = MqttClientDisconnectType.NotClean; try { await _onStart(); _logger.Info("Client '{0}': Session started.", ClientId); _channelAdapter.ReadingPacketStartedCallback = OnAdapterReadingPacketStarted; _channelAdapter.ReadingPacketCompletedCallback = OnAdapterReadingPacketCompleted; Session.WillMessage = ConnectPacket.WillMessage; Task.Run(() => SendPendingPacketsAsync(_cancellationToken.Token), _cancellationToken.Token).Forget(_logger); // TODO: Change to single thread in SessionManager. Or use SessionManager and stats from KeepAliveMonitor. _keepAliveMonitor.Start(ConnectPacket.KeepAlivePeriod, _cancellationToken.Token); await SendAsync( _channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext) ).ConfigureAwait(false); Session.IsCleanSession = false; while (!_cancellationToken.IsCancellationRequested) { var packet = await _channelAdapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationToken.Token).ConfigureAwait(false); if (packet == null) { // The client has closed the connection gracefully. break; } Interlocked.Increment(ref _sentPacketsCount); _lastPacketReceivedTimestamp = DateTime.UtcNow; if (!(packet is MqttPingReqPacket || packet is MqttPingRespPacket)) { _lastNonKeepAlivePacketReceivedTimestamp = _lastPacketReceivedTimestamp; } _keepAliveMonitor.PacketReceived(); if (packet is MqttPublishPacket publishPacket) { await HandleIncomingPublishPacketAsync(publishPacket).ConfigureAwait(false); continue; } if (packet is MqttPubRelPacket pubRelPacket) { var pubCompPacket = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier, ReasonCode = MqttPubCompReasonCode.Success }; await SendAsync(pubCompPacket).ConfigureAwait(false); continue; } if (packet is MqttSubscribePacket subscribePacket) { await HandleIncomingSubscribePacketAsync(subscribePacket).ConfigureAwait(false); continue; } if (packet is MqttUnsubscribePacket unsubscribePacket) { await HandleIncomingUnsubscribePacketAsync(unsubscribePacket).ConfigureAwait(false); continue; } if (packet is MqttPingReqPacket) { await SendAsync(new MqttPingRespPacket()).ConfigureAwait(false); continue; } if (packet is MqttDisconnectPacket) { Session.WillMessage = null; disconnectType = MqttClientDisconnectType.Clean; StopInternal(); break; } _packetDispatcher.Dispatch(packet); } } catch (OperationCanceledException) { } catch (Exception exception) { if (exception is MqttCommunicationException) { _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId); } else { _logger.Error(exception, "Client '{0}': Error while receiving client packets.", ClientId); } StopInternal(); } finally { if (_isTakeover) { disconnectType = MqttClientDisconnectType.Takeover; } if (Session.WillMessage != null) { _sessionsManager.DispatchApplicationMessage(Session.WillMessage, this); Session.WillMessage = null; } _packetDispatcher.Reset(); _channelAdapter.ReadingPacketStartedCallback = null; _channelAdapter.ReadingPacketCompletedCallback = null; _logger.Info("Client '{0}': Connection stopped.", ClientId); _packageReceiverTask = null; try { await _onStop(disconnectType); } catch (Exception e) { _logger.Error(e, "client '{0}': Error while cleaning up", ClientId); } } }