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.
 
 
 
 

236 lines
9.4 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> _sessions = new Dictionary<string, MqttClientSession>();
  19. private readonly SemaphoreSlim _sessionsSemaphore = new SemaphoreSlim(1, 1);
  20. private readonly MqttServerOptions _options;
  21. private readonly ILogger<MqttClientSessionsManager> _logger;
  22. private readonly IMqttClientSesssionFactory _clientSesssionFactory;
  23. private readonly IMqttClientRetainedMessageManager _clientRetainedMessageManager;
  24. public MqttClientSessionsManager(
  25. IOptions<MqttServerOptions> options,
  26. ILogger<MqttClientSessionsManager> logger,
  27. IMqttClientSesssionFactory clientSesssionFactory,
  28. IMqttClientRetainedMessageManager clientRetainedMessageManager)
  29. {
  30. _logger = logger ?? throw new ArgumentNullException(nameof(logger));
  31. _options = options.Value ?? throw new ArgumentNullException(nameof(options));
  32. _clientSesssionFactory = clientSesssionFactory ?? throw new ArgumentNullException(nameof(clientSesssionFactory));
  33. _clientRetainedMessageManager = clientRetainedMessageManager ?? throw new ArgumentNullException(nameof(clientRetainedMessageManager));
  34. }
  35. public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
  36. public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
  37. public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
  38. public async Task RunClientSessionAsync(IMqttCommunicationAdapter clientAdapter, CancellationToken cancellationToken)
  39. {
  40. var clientId = string.Empty;
  41. try
  42. {
  43. if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false) is MqttConnectPacket connectPacket))
  44. {
  45. throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
  46. }
  47. clientId = connectPacket.ClientId;
  48. // Switch to the required protocol version before sending any response.
  49. clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
  50. var connectReturnCode = ValidateConnection(connectPacket);
  51. if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
  52. {
  53. await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket
  54. {
  55. ConnectReturnCode = connectReturnCode
  56. }).ConfigureAwait(false);
  57. return;
  58. }
  59. var clientSession = await GetOrCreateClientSessionAsync(connectPacket);
  60. await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket
  61. {
  62. ConnectReturnCode = connectReturnCode,
  63. IsSessionPresent = clientSession.IsExistingSession
  64. }).ConfigureAwait(false);
  65. ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(new ConnectedMqttClient
  66. {
  67. ClientId = clientId,
  68. ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
  69. }));
  70. using (_logger.BeginScope(clientId))
  71. {
  72. await clientSession.Session.RunAsync(connectPacket.WillMessage, clientAdapter).ConfigureAwait(false);
  73. }
  74. }
  75. catch (Exception exception)
  76. {
  77. _logger.LogError(new EventId(), exception, exception.Message);
  78. }
  79. finally
  80. {
  81. try
  82. {
  83. await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
  84. }
  85. catch (Exception)
  86. {
  87. // ignored
  88. }
  89. ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(new ConnectedMqttClient
  90. {
  91. ClientId = clientId,
  92. ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
  93. }));
  94. }
  95. }
  96. public async Task StopAsync()
  97. {
  98. await _sessionsSemaphore.WaitAsync().ConfigureAwait(false);
  99. try
  100. {
  101. foreach (var session in _sessions)
  102. {
  103. await session.Value.StopAsync();
  104. }
  105. _sessions.Clear();
  106. }
  107. finally
  108. {
  109. _sessionsSemaphore.Release();
  110. }
  111. }
  112. public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
  113. {
  114. await _sessionsSemaphore.WaitAsync().ConfigureAwait(false);
  115. try
  116. {
  117. return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
  118. {
  119. ClientId = s.Value.ClientId,
  120. ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311
  121. }).ToList();
  122. }
  123. finally
  124. {
  125. _sessionsSemaphore.Release();
  126. }
  127. }
  128. public async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
  129. {
  130. try
  131. {
  132. var interceptorContext = new MqttApplicationMessageInterceptorContext
  133. {
  134. ApplicationMessage = applicationMessage
  135. };
  136. _options.ApplicationMessageInterceptor?.Invoke(interceptorContext);
  137. applicationMessage = interceptorContext.ApplicationMessage;
  138. if (applicationMessage.Retain)
  139. {
  140. await _clientRetainedMessageManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
  141. }
  142. var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, applicationMessage);
  143. ApplicationMessageReceived?.Invoke(this, eventArgs);
  144. }
  145. catch (Exception exception)
  146. {
  147. _logger.LogError(new EventId(), exception, "Error while processing application message");
  148. }
  149. lock (_sessions)
  150. {
  151. foreach (var clientSession in _sessions.Values.ToList())
  152. {
  153. clientSession.EnqueuePublishPacket(applicationMessage.ToPublishPacket());
  154. }
  155. }
  156. }
  157. public Task<List<MqttApplicationMessage>> GetRetainedMessagesAsync(MqttSubscribePacket subscribePacket)
  158. {
  159. return _clientRetainedMessageManager.GetSubscribedMessagesAsync(subscribePacket);
  160. }
  161. private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
  162. {
  163. if (_options.ConnectionValidator != null)
  164. {
  165. return _options.ConnectionValidator(connectPacket);
  166. }
  167. return MqttConnectReturnCode.ConnectionAccepted;
  168. }
  169. private async Task<GetOrCreateClientSessionResult> GetOrCreateClientSessionAsync(MqttConnectPacket connectPacket)
  170. {
  171. await _sessionsSemaphore.WaitAsync().ConfigureAwait(false);
  172. try
  173. {
  174. var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
  175. if (isSessionPresent)
  176. {
  177. if (connectPacket.CleanSession)
  178. {
  179. _sessions.Remove(connectPacket.ClientId);
  180. await clientSession.StopAsync();
  181. clientSession = null;
  182. _logger.LogTrace("Stopped existing session of client '{0}'.", connectPacket.ClientId);
  183. }
  184. else
  185. {
  186. _logger.LogTrace("Reusing existing session of client '{0}'.", connectPacket.ClientId);
  187. }
  188. }
  189. var isExistingSession = true;
  190. if (clientSession == null)
  191. {
  192. isExistingSession = false;
  193. clientSession = _clientSesssionFactory.CreateClientSession(connectPacket.ClientId, this);
  194. _sessions[connectPacket.ClientId] = clientSession;
  195. _logger.LogTrace("Created a new session for client '{0}'.", connectPacket.ClientId);
  196. }
  197. return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
  198. }
  199. finally
  200. {
  201. _sessionsSemaphore.Release();
  202. }
  203. }
  204. }
  205. }