You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

192 lines
7.8 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using MQTTnet.Core.Adapter;
  7. using MQTTnet.Core.Exceptions;
  8. using MQTTnet.Core.Internal;
  9. using MQTTnet.Core.Packets;
  10. using MQTTnet.Core.Protocol;
  11. using MQTTnet.Core.Serializer;
  12. using Microsoft.Extensions.Logging;
  13. using Microsoft.Extensions.Options;
  14. namespace MQTTnet.Core.Server
  15. {
  16. public sealed class MqttClientSessionsManager
  17. {
  18. private readonly Dictionary<string, MqttClientSession> _clientSessions = new Dictionary<string, MqttClientSession>();
  19. private readonly ILogger<MqttClientSessionsManager> _logger;
  20. private readonly IMqttClientSesssionFactory _mqttClientSesssionFactory;
  21. public MqttClientSessionsManager(IOptions<MqttServerOptions> options, ILogger<MqttClientSessionsManager> logger, MqttClientRetainedMessagesManager retainedMessagesManager, IMqttClientSesssionFactory mqttClientSesssionFactory)
  22. {
  23. _logger = logger ?? throw new ArgumentNullException(nameof(logger));
  24. Options = options.Value ?? throw new ArgumentNullException(nameof(options));
  25. RetainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(options));
  26. _mqttClientSesssionFactory = mqttClientSesssionFactory ?? throw new ArgumentNullException(nameof(mqttClientSesssionFactory));
  27. }
  28. public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
  29. public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
  30. public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
  31. public MqttClientRetainedMessagesManager RetainedMessagesManager { get; }
  32. public MqttServerOptions Options { get; }
  33. public async Task RunClientSessionAsync(IMqttCommunicationAdapter clientAdapter)
  34. {
  35. var clientId = string.Empty;
  36. try
  37. {
  38. if (!(await clientAdapter.ReceivePacketAsync(Options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false) is MqttConnectPacket connectPacket))
  39. {
  40. throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
  41. }
  42. clientId = connectPacket.ClientId;
  43. // Switch to the required protocol version before sending any response.
  44. clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
  45. var connectReturnCode = ValidateConnection(connectPacket);
  46. if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
  47. {
  48. await clientAdapter.SendPacketsAsync(Options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
  49. {
  50. ConnectReturnCode = connectReturnCode
  51. }).ConfigureAwait(false);
  52. return;
  53. }
  54. var clientSession = GetOrCreateClientSession(connectPacket);
  55. await clientAdapter.SendPacketsAsync(Options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
  56. {
  57. ConnectReturnCode = connectReturnCode,
  58. IsSessionPresent = clientSession.IsExistingSession
  59. }).ConfigureAwait(false);
  60. ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(new ConnectedMqttClient
  61. {
  62. ClientId = clientId,
  63. ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
  64. }));
  65. using (_logger.BeginScope(clientId))
  66. {
  67. await clientSession.Session.RunAsync(connectPacket.WillMessage, clientAdapter).ConfigureAwait(false);
  68. }
  69. }
  70. catch (Exception exception)
  71. {
  72. _logger.LogError(new EventId(), exception, exception.Message);
  73. }
  74. finally
  75. {
  76. try
  77. {
  78. await clientAdapter.DisconnectAsync(Options.DefaultCommunicationTimeout).ConfigureAwait(false);
  79. }
  80. catch (Exception)
  81. {
  82. //ignored
  83. }
  84. ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(new ConnectedMqttClient
  85. {
  86. ClientId = clientId,
  87. ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
  88. }));
  89. }
  90. }
  91. public void Clear()
  92. {
  93. lock (_clientSessions)
  94. {
  95. _clientSessions.Clear();
  96. }
  97. }
  98. public IList<ConnectedMqttClient> GetConnectedClients()
  99. {
  100. lock (_clientSessions)
  101. {
  102. return _clientSessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
  103. {
  104. ClientId = s.Value.ClientId,
  105. ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311
  106. }).ToList();
  107. }
  108. }
  109. public void DispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
  110. {
  111. try
  112. {
  113. var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, applicationMessage);
  114. ApplicationMessageReceived?.Invoke(this, eventArgs);
  115. }
  116. catch (Exception exception)
  117. {
  118. _logger.LogError(new EventId(), exception, "Error while processing application message");
  119. }
  120. lock (_clientSessions)
  121. {
  122. foreach (var clientSession in _clientSessions.Values.ToList())
  123. {
  124. clientSession.EnqueuePublishPacket(applicationMessage.ToPublishPacket());
  125. }
  126. }
  127. }
  128. private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
  129. {
  130. if (Options.ConnectionValidator != null)
  131. {
  132. return Options.ConnectionValidator(connectPacket);
  133. }
  134. return MqttConnectReturnCode.ConnectionAccepted;
  135. }
  136. private GetOrCreateClientSessionResult GetOrCreateClientSession(MqttConnectPacket connectPacket)
  137. {
  138. lock (_clientSessions)
  139. {
  140. var isSessionPresent = _clientSessions.TryGetValue(connectPacket.ClientId, out var clientSession);
  141. if (isSessionPresent)
  142. {
  143. if (connectPacket.CleanSession)
  144. {
  145. _clientSessions.Remove(connectPacket.ClientId);
  146. clientSession.Dispose();
  147. clientSession = null;
  148. _logger.LogTrace("Disposed existing session of client '{0}'.", connectPacket.ClientId);
  149. }
  150. else
  151. {
  152. _logger.LogTrace("Reusing existing session of client '{0}'.", connectPacket.ClientId);
  153. }
  154. }
  155. var isExistingSession = true;
  156. if (clientSession == null)
  157. {
  158. isExistingSession = false;
  159. clientSession = _mqttClientSesssionFactory.CreateClientSession(connectPacket.ClientId, this);
  160. _clientSessions[connectPacket.ClientId] = clientSession;
  161. _logger.LogTrace("Created a new session for client '{0}'.", connectPacket.ClientId);
  162. }
  163. return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
  164. }
  165. }
  166. }
  167. }