Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

285 lignes
11 KiB

  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using MQTTnet.Adapter;
  7. using MQTTnet.Diagnostics;
  8. using MQTTnet.Exceptions;
  9. using MQTTnet.Internal;
  10. using MQTTnet.Packets;
  11. using MQTTnet.Protocol;
  12. namespace MQTTnet.Server
  13. {
  14. public sealed class MqttClientSessionsManager : IDisposable
  15. {
  16. private readonly ConcurrentDictionary<string, MqttClientSession> _sessions = new ConcurrentDictionary<string, MqttClientSession>();
  17. private readonly AsyncLock _sessionPreparationLock = new AsyncLock();
  18. private readonly MqttRetainedMessagesManager _retainedMessagesManager;
  19. private readonly IMqttServerOptions _options;
  20. private readonly IMqttNetChildLogger _logger;
  21. public MqttClientSessionsManager(IMqttServerOptions options, MqttServer server, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetChildLogger logger)
  22. {
  23. if (logger == null) throw new ArgumentNullException(nameof(logger));
  24. _logger = logger.CreateChildLogger(nameof(MqttClientSessionsManager));
  25. _options = options ?? throw new ArgumentNullException(nameof(options));
  26. Server = server ?? throw new ArgumentNullException(nameof(server));
  27. _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
  28. }
  29. public MqttServer Server { get; }
  30. public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
  31. {
  32. var clientId = string.Empty;
  33. var wasCleanDisconnect = false;
  34. try
  35. {
  36. if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken)
  37. .ConfigureAwait(false) is MqttConnectPacket connectPacket))
  38. {
  39. throw new MqttProtocolViolationException(
  40. "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, new[]
  49. {
  50. new MqttConnAckPacket
  51. {
  52. ConnectReturnCode = connectReturnCode
  53. }
  54. }, cancellationToken).ConfigureAwait(false);
  55. return;
  56. }
  57. var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false);
  58. var clientSession = result.Session;
  59. await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[]
  60. {
  61. new MqttConnAckPacket
  62. {
  63. ConnectReturnCode = connectReturnCode,
  64. IsSessionPresent = result.IsExistingSession
  65. }
  66. }, cancellationToken).ConfigureAwait(false);
  67. Server.OnClientConnected(clientId);
  68. wasCleanDisconnect = await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
  69. }
  70. catch (OperationCanceledException)
  71. {
  72. }
  73. catch (Exception exception)
  74. {
  75. _logger.Error(exception, exception.Message);
  76. }
  77. finally
  78. {
  79. try
  80. {
  81. await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
  82. clientAdapter.Dispose();
  83. }
  84. catch (Exception exception)
  85. {
  86. _logger.Error(exception, exception.Message);
  87. }
  88. if (!_options.EnablePersistentSessions)
  89. {
  90. DeleteSession(clientId);
  91. }
  92. Server.OnClientDisconnected(clientId, wasCleanDisconnect);
  93. }
  94. }
  95. public Task StopAsync()
  96. {
  97. foreach (var session in _sessions)
  98. {
  99. session.Value.Stop(MqttClientDisconnectType.NotClean);
  100. }
  101. _sessions.Clear();
  102. return Task.FromResult(0);
  103. }
  104. public Task<IList<IMqttClientSessionStatus>> GetClientStatusAsync()
  105. {
  106. var result = new List<IMqttClientSessionStatus>();
  107. foreach (var session in _sessions)
  108. {
  109. var status = new MqttClientSessionStatus(this, session.Value);
  110. session.Value.FillStatus(status);
  111. result.Add(status);
  112. }
  113. return Task.FromResult((IList<IMqttClientSessionStatus>)result);
  114. }
  115. public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
  116. {
  117. Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage));
  118. }
  119. public Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
  120. {
  121. if (clientId == null) throw new ArgumentNullException(nameof(clientId));
  122. if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
  123. if (!_sessions.TryGetValue(clientId, out var session))
  124. {
  125. throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
  126. }
  127. return session.SubscribeAsync(topicFilters);
  128. }
  129. public Task UnsubscribeAsync(string clientId, IList<string> topicFilters)
  130. {
  131. if (clientId == null) throw new ArgumentNullException(nameof(clientId));
  132. if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
  133. if (!_sessions.TryGetValue(clientId, out var session))
  134. {
  135. throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
  136. }
  137. return session.UnsubscribeAsync(topicFilters);
  138. }
  139. public void DeleteSession(string clientId)
  140. {
  141. _sessions.TryRemove(clientId, out _);
  142. _logger.Verbose("Session for client '{0}' deleted.", clientId);
  143. }
  144. public void Dispose()
  145. {
  146. _sessionPreparationLock?.Dispose();
  147. }
  148. private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
  149. {
  150. if (_options.ConnectionValidator == null)
  151. {
  152. return MqttConnectReturnCode.ConnectionAccepted;
  153. }
  154. var context = new MqttConnectionValidatorContext(
  155. connectPacket.ClientId,
  156. connectPacket.Username,
  157. connectPacket.Password,
  158. connectPacket.WillMessage);
  159. _options.ConnectionValidator(context);
  160. return context.ReturnCode;
  161. }
  162. private async Task<GetOrCreateClientSessionResult> PrepareClientSessionAsync(MqttConnectPacket connectPacket)
  163. {
  164. using (await _sessionPreparationLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
  165. {
  166. var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
  167. if (isSessionPresent)
  168. {
  169. if (connectPacket.CleanSession)
  170. {
  171. _sessions.TryRemove(connectPacket.ClientId, out _);
  172. clientSession.Stop(MqttClientDisconnectType.Clean);
  173. clientSession.Dispose();
  174. clientSession = null;
  175. _logger.Verbose("Stopped existing session of client '{0}'.", connectPacket.ClientId);
  176. }
  177. else
  178. {
  179. _logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
  180. }
  181. }
  182. var isExistingSession = true;
  183. if (clientSession == null)
  184. {
  185. isExistingSession = false;
  186. clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _retainedMessagesManager, _logger);
  187. _sessions[connectPacket.ClientId] = clientSession;
  188. _logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
  189. }
  190. return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
  191. }
  192. }
  193. private async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
  194. {
  195. try
  196. {
  197. var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage);
  198. if (interceptorContext.CloseConnection)
  199. {
  200. senderClientSession.Stop(MqttClientDisconnectType.NotClean);
  201. }
  202. if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
  203. {
  204. return;
  205. }
  206. Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage);
  207. if (applicationMessage.Retain)
  208. {
  209. await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
  210. }
  211. foreach (var clientSession in _sessions.Values)
  212. {
  213. clientSession.EnqueueApplicationMessage(applicationMessage);
  214. }
  215. }
  216. catch (Exception exception)
  217. {
  218. _logger.Error(exception, "Error while processing application message");
  219. }
  220. }
  221. private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
  222. {
  223. var interceptorContext = new MqttApplicationMessageInterceptorContext(
  224. senderClientSession?.ClientId,
  225. applicationMessage);
  226. var interceptor = _options.ApplicationMessageInterceptor;
  227. if (interceptor == null)
  228. {
  229. return interceptorContext;
  230. }
  231. interceptor(interceptorContext);
  232. return interceptorContext;
  233. }
  234. }
  235. }