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().ConfigureAwait(false); _clientSessionsManager = new MqttClientSessionsManager(Options, _retainedMessagesManager, _cancellationTokenSource.Token, _eventDispatcher, _logger); _clientSessionsManager.Start(); foreach (var adapter in _adapters) { adapter.ClientAccepted += OnClientAccepted; await adapter.StartAsync(Options).ConfigureAwait(false); } _logger.Info("Started."); Started?.Invoke(this, EventArgs.Empty); }
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; _packetIdentifierProvider.Reset(); _packetDispatcher.Reset(); _cancellationTokenSource = new CancellationTokenSource(); _disconnectGate = 0; _adapter = _adapterFactory.CreateClientAdapter(options, _logger); _logger.Verbose($"Trying to connect with server ({Options.ChannelOptions})."); await _adapter.ConnectAsync(Options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false); _logger.Verbose("Connection with server established."); StartReceivingPackets(_cancellationTokenSource.Token); var connectResponse = await AuthenticateAsync(options.WillMessage, _cancellationTokenSource.Token).ConfigureAwait(false); _logger.Verbose("MQTT connection with server established."); _sendTracker.Restart(); if (Options.KeepAlivePeriod != TimeSpan.Zero) { StartSendingKeepAliveMessages(_cancellationTokenSource.Token); } IsConnected = true; Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent)); _logger.Info("Connected."); return(new MqttClientConnectResult(connectResponse.IsSessionPresent)); } catch (Exception exception) { _logger.Error(exception, "Error while connecting with server."); if (!DisconnectIsPending()) { await DisconnectInternalAsync(null, exception).ConfigureAwait(false); } throw; } }
private void Stop(MqttClientDisconnectType type, bool isInsideSession) { try { var cts = _cancellationTokenSource; if (cts == null || cts.IsCancellationRequested) { return; } _cancellationTokenSource?.Cancel(false); _wasCleanDisconnect = type == MqttClientDisconnectType.Clean; if (_willMessage != null && !_wasCleanDisconnect) { _sessionsManager.EnqueueApplicationMessage(this, _willMessage.ToPublishPacket()); } _willMessage = null; if (!isInsideSession) { _workerTask?.GetAwaiter().GetResult(); } } finally { _logger.Info("Client '{0}': Disconnected (clean={1}).", ClientId, _wasCleanDisconnect); _eventDispatcher.OnClientDisconnected(ClientId, _wasCleanDisconnect); } }
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 Socket(_addressFamily, SocketType.Stream, ProtocolType.Tcp); _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 void Stop(MqttClientDisconnectType type) { try { var cts = _cancellationTokenSource; if (cts == null || cts.IsCancellationRequested) { return; } _wasCleanDisconnect = type == MqttClientDisconnectType.Clean; _cancellationTokenSource?.Cancel(false); if (_willMessage != null && !_wasCleanDisconnect) { _sessionsManager.EnqueueApplicationMessage(this, _willMessage.ToPublishPacket()); } _willMessage = null; } finally { _logger.Info("Client '{0}': Session stopped.", ClientId); } }
private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage) { var saveIsRequired = false; lock (_messages) { if (applicationMessage.Payload?.Length == 0) { saveIsRequired = _messages.Remove(applicationMessage.Topic); _logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); } else { if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage)) { _messages[applicationMessage.Topic] = applicationMessage; saveIsRequired = true; } else { if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0])) { _messages[applicationMessage.Topic] = applicationMessage; saveIsRequired = true; } } _logger.Info("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic); } } if (!saveIsRequired) { _logger.Verbose("Skipped saving retained messages because no changes were detected."); } if (saveIsRequired && _options.Storage != null) { List <MqttApplicationMessage> messages; lock (_messages) { messages = _messages.Values.ToList(); } await _options.Storage.SaveRetainedMessagesAsync(messages).ConfigureAwait(false); } }
public async Task StartAsync(IMqttManagedClientOptions 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; if (Options.Storage != null) { _storageManager = new MqttManagedClientStorageManager(Options.Storage); var messages = await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false); foreach (var message in messages) { _messageQueue.Enqueue(message); } } _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(() => 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("Started"); }
public void Start() { var boundIp = _options.BoundInterNetworkAddress; if (_addressFamily == AddressFamily.InterNetworkV6) { boundIp = _options.BoundInterNetworkV6Address; } _socket = new Socket(_addressFamily, SocketType.Stream, ProtocolType.Tcp); _socket.Bind(new IPEndPoint(boundIp, _options.Port)); _logger.Info($"Starting TCP listener for {_socket.LocalEndPoint} TLS={_tlsCertificate != null}."); _socket.Listen(_options.ConnectionBacklog); Task.Run(AcceptClientConnectionsAsync, _cancellationToken); }
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 Socket(_addressFamily, SocketType.Stream, ProtocolType.Tcp); // 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.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); } if (_options.NoDelay) { _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.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 RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) { var clientId = string.Empty; try { var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); if (firstPacket == null) { return; } if (!(firstPacket is MqttConnectPacket connectPacket)) { throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); } clientId = connectPacket.ClientId; // Switch to the required protocol version before sending any response. clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion; var connectReturnCode = ValidateConnection(connectPacket, clientAdapter); if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) { await clientAdapter.SendPacketAsync( new MqttConnAckPacket { ConnectReturnCode = connectReturnCode }, cancellationToken).ConfigureAwait(false); return; } var result = PrepareClientSession(connectPacket); await clientAdapter.SendPacketAsync( new MqttConnAckPacket { ConnectReturnCode = connectReturnCode, IsSessionPresent = result.IsExistingSession }, cancellationToken).ConfigureAwait(false); _logger.Info("Client '{0}': Connected.", clientId); _eventDispatcher.OnClientConnected(clientId); await result.Session.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); } catch (OperationCanceledException) { } catch (Exception exception) { _logger.Error(exception, exception.Message); } finally { if (!_options.EnablePersistentSessions) { DeleteSession(clientId); } } }
private async Task <MqttClientDisconnectType> RunInternalAsync() { var disconnectType = MqttClientDisconnectType.NotClean; try { _logger.Info("Client '{0}': Session started.", ClientId); _channelAdapter.ReadingPacketStartedCallback = OnAdapterReadingPacketStarted; _channelAdapter.ReadingPacketCompletedCallback = OnAdapterReadingPacketCompleted; Session.WillMessage = _connectPacket.WillMessage; #pragma warning disable 4014 Task.Run(() => SendPendingPacketsAsync(_cancellationToken.Token), _cancellationToken.Token); #pragma warning restore 4014 // TODO: Change to single thread in SessionManager. Or use SessionManager and stats from KeepAliveMonitor. _keepAliveMonitor.Start(_connectPacket.KeepAlivePeriod, _cancellationToken.Token); await SendAsync( new MqttConnAckPacket { ReturnCode = MqttConnectReturnCode.ConnectionAccepted, ReasonCode = MqttConnectReasonCode.Success, IsSessionPresent = !Session.IsCleanSession }).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}': Unhandled exception while receiving client packets.", ClientId); } StopInternal(); } finally { if (Session.WillMessage != null) { _sessionsManager.DispatchApplicationMessage(Session.WillMessage, this); Session.WillMessage = null; } _packetDispatcher.Reset(); _channelAdapter.ReadingPacketStartedCallback = null; _channelAdapter.ReadingPacketCompletedCallback = null; _logger.Info("Client '{0}': Session stopped.", ClientId); _packageReceiverTask = null; } return(disconnectType); }
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."); _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; } }