public async Task StartAsync(IMqttServerOptions options) { Options = options ?? throw new ArgumentNullException(nameof(options)); if (_cancellationTokenSource != null) { throw new InvalidOperationException("The server is already started."); } _cancellationTokenSource = new CancellationTokenSource(); _retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger); await _retainedMessagesManager.LoadMessagesAsync(); _clientSessionsManager = new MqttClientSessionsManager(Options, _retainedMessagesManager, _logger) { ClientConnectedCallback = OnClientConnected, ClientDisconnectedCallback = OnClientDisconnected, ClientSubscribedTopicCallback = OnClientSubscribedTopic, ClientUnsubscribedTopicCallback = OnClientUnsubscribedTopic, ApplicationMessageReceivedCallback = OnApplicationMessageReceived }; foreach (var adapter in _adapters) { adapter.ClientAccepted += OnClientAccepted; await adapter.StartAsync(Options); } _logger.Info <MqttServer>("Started."); Started?.Invoke(this, new MqttServerStartedEventArgs()); }
public async Task StopAsync(bool wasCleanDisconnect = false) { try { if (_cancellationTokenSource == null) { return; } _wasCleanDisconnect = wasCleanDisconnect; _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) { await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false); } } }
public async Task StopAsync() { try { if (_cancellationTokenSource == null) { return; } _cancellationTokenSource?.Cancel(false); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; if (_adapter != null) { await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); _adapter = null; } _logger.Info <MqttClientSession>("Client '{0}': Session stopped.", ClientId); } finally { var willMessage = _willMessage; if (willMessage != null) { _willMessage = null; // clear willmessage so it is send just once await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false); } } }
public async Task <MqttClientConnectResult> ConnectAsync(IMqttClientOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (options.ChannelOptions == null) { throw new ArgumentException("ChannelOptions are not set."); } ThrowIfConnected("It is not allowed to connect with a server after the connection is established."); try { _options = options; _cancellationTokenSource = new CancellationTokenSource(); _packetIdentifierProvider.Reset(); _packetDispatcher.Reset(); _adapter = _adapterFactory.CreateClientAdapter(options, _logger); _logger.Verbose <MqttClient>("Trying to connect with server."); await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); _logger.Verbose <MqttClient>("Connection with server established."); await StartReceivingPacketsAsync().ConfigureAwait(false); var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false); _logger.Verbose <MqttClient>("MQTT connection with server established."); _sendTracker.Restart(); if (_options.KeepAlivePeriod != TimeSpan.Zero) { StartSendingKeepAliveMessages(); } IsConnected = true; Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent)); _logger.Info <MqttClient>("Connected."); return(new MqttClientConnectResult(connectResponse.IsSessionPresent)); } catch (Exception exception) { _logger.Error <MqttClient>(exception, "Error while connecting with server."); await DisconnectInternalAsync(null, exception).ConfigureAwait(false); throw; } }
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)); } }
public async Task StartAsync(IManagedMqttClientOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (options.ClientOptions == null) { throw new ArgumentException("The client options are not set.", nameof(options)); } if (!options.ClientOptions.CleanSession) { throw new NotSupportedException("The managed client does not support existing sessions."); } if (_connectionCancellationToken != null) { throw new InvalidOperationException("The managed client is already started."); } _options = options; await _storageManager.SetStorageAsync(_options.Storage).ConfigureAwait(false); if (_options.Storage != null) { var loadedMessages = await _options.Storage.LoadQueuedMessagesAsync().ConfigureAwait(false); foreach (var loadedMessage in loadedMessages) { _messageQueue.Add(loadedMessage); } } _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), _connectionCancellationToken.Token).ConfigureAwait(false); #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"); }
private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage) { var saveIsRequired = false; if (applicationMessage.Payload?.Any() == false) { saveIsRequired = _retainedMessages.Remove(applicationMessage.Topic); _logger.Info <MqttRetainedMessagesManager>("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); } else { if (!_retainedMessages.ContainsKey(applicationMessage.Topic)) { _retainedMessages[applicationMessage.Topic] = applicationMessage; saveIsRequired = true; } else { var existingMessage = _retainedMessages[applicationMessage.Topic]; if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0])) { _retainedMessages[applicationMessage.Topic] = applicationMessage; saveIsRequired = true; } } _logger.Info <MqttRetainedMessagesManager>("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic); } if (!saveIsRequired) { _logger.Trace <MqttRetainedMessagesManager>("Skipped saving retained messages because no changes were detected."); } if (saveIsRequired && _options.Storage != null) { await _options.Storage.SaveRetainedMessagesAsync(_retainedMessages.Values.ToList()); } }
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(); } }
public async Task StartAsync(IMqttServerOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); if (_cancellationTokenSource != null) { throw new InvalidOperationException("The server is already started."); } _cancellationTokenSource = new CancellationTokenSource(); _retainedMessagesManager = new MqttRetainedMessagesManager(_options, _logger); _clientSessionsManager = new MqttClientSessionsManager(_options, _retainedMessagesManager, this, _logger); await _retainedMessagesManager.LoadMessagesAsync(); foreach (var adapter in _adapters) { adapter.ClientAccepted += OnClientAccepted; await adapter.StartAsync(_options); } _logger.Info <MqttServer>("Started."); Started?.Invoke(this, new MqttServerStartedEventArgs()); }
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); } }
private async Task DisconnectInternalAsync(Exception exception) { await _disconnectLock.WaitAsync(); var clientWasConnected = IsConnected; try { IsConnected = false; if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested) { return; } _cancellationTokenSource.Cancel(false); if (_packetReceiverTask != null) { Task.WaitAll(_packetReceiverTask); } if (_keepAliveMessageSenderTask != null) { Task.WaitAll(_keepAliveMessageSenderTask); } await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); _logger.Trace <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; _disconnectLock.Release(); _logger.Info <MqttClient>("Disconnected."); Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception)); } }
public async Task <MqttClientAuthenticateResult> ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (options.ChannelOptions == null) { throw new ArgumentException("ChannelOptions are not set."); } ThrowIfConnected("It is not allowed to connect with a server after the connection is established."); ThrowIfDisposed(); MqttClientAuthenticateResult authenticateResult = null; try { Options = options; _packetIdentifierProvider.Reset(); _packetDispatcher.Reset(); _backgroundCancellationTokenSource = new CancellationTokenSource(); var backgroundCancellationToken = _backgroundCancellationTokenSource.Token; _disconnectGate = 0; var adapter = _adapterFactory.CreateClientAdapter(options, _logger); _adapter = adapter; using (var combined = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken)) { _logger.Verbose($"Trying to connect with server '{options.ChannelOptions}' (Timeout={options.CommunicationTimeout})."); await _adapter.ConnectAsync(options.CommunicationTimeout, combined.Token).ConfigureAwait(false); _logger.Verbose("Connection with server established."); _publishPacketReceiverQueue = new AsyncQueue <MqttPublishPacket>(); _publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken); _packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken); authenticateResult = await AuthenticateAsync(adapter, options.WillMessage, combined.Token).ConfigureAwait(false); } _sendTracker.Restart(); _receiveTracker.Restart(); if (Options.KeepAlivePeriod != TimeSpan.Zero) { _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(backgroundCancellationToken), backgroundCancellationToken); } IsConnected = true; _logger.Info("Connected."); var connectedHandler = ConnectedHandler; if (connectedHandler != null) { await connectedHandler.HandleConnectedAsync(new MqttClientConnectedEventArgs(authenticateResult)).ConfigureAwait(false); } return(authenticateResult); } catch (Exception exception) { _logger.Error(exception, "Error while connecting with server."); if (!DisconnectIsPending()) { await DisconnectInternalAsync(null, exception, authenticateResult).ConfigureAwait(false); } throw; } }
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); } } }
public async Task ConnectAsync(TimeSpan timeout) { _logger.Info <MqttChannelAdapter>("Connecting [Timeout={0}]", timeout); await ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout)); }