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.
 
 
 
 

247 line
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.Adapter;
  7. using MQTTnet.Diagnostics;
  8. using MQTTnet.Exceptions;
  9. using MQTTnet.Packets;
  10. using MQTTnet.Protocol;
  11. using MQTTnet.Serializer;
  12. namespace MQTTnet.Server
  13. {
  14. public sealed class MqttClientSessionsManager
  15. {
  16. private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
  17. private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
  18. private readonly IMqttServerOptions _options;
  19. private readonly MqttServer _server;
  20. private readonly MqttRetainedMessagesManager _retainedMessagesManager;
  21. private readonly IMqttNetLogger _logger;
  22. public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, MqttServer server, IMqttNetLogger logger)
  23. {
  24. _server = server ?? throw new ArgumentNullException(nameof(server));
  25. _logger = logger ?? throw new ArgumentNullException(nameof(logger));
  26. _options = options ?? throw new ArgumentNullException(nameof(options));
  27. _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
  28. }
  29. public async Task RunClientSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
  30. {
  31. var clientId = string.Empty;
  32. try
  33. {
  34. if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false) is MqttConnectPacket connectPacket))
  35. {
  36. throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
  37. }
  38. clientId = connectPacket.ClientId;
  39. // Switch to the required protocol version before sending any response.
  40. clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
  41. var connectReturnCode = ValidateConnection(connectPacket);
  42. if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
  43. {
  44. await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket
  45. {
  46. ConnectReturnCode = connectReturnCode
  47. }).ConfigureAwait(false);
  48. return;
  49. }
  50. var clientSession = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false);
  51. await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket
  52. {
  53. ConnectReturnCode = connectReturnCode,
  54. IsSessionPresent = clientSession.IsExistingSession
  55. }).ConfigureAwait(false);
  56. _server.OnClientConnected(new ConnectedMqttClient
  57. {
  58. ClientId = clientId,
  59. ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
  60. });
  61. await clientSession.Session.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
  62. }
  63. catch (Exception exception)
  64. {
  65. _logger.Error<MqttClientSessionsManager>(exception, exception.Message);
  66. }
  67. finally
  68. {
  69. try
  70. {
  71. await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
  72. }
  73. catch (Exception)
  74. {
  75. // ignored
  76. }
  77. _server.OnClientDisconnected(new ConnectedMqttClient
  78. {
  79. ClientId = clientId,
  80. ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
  81. });
  82. }
  83. }
  84. public async Task StopAsync()
  85. {
  86. await _semaphore.WaitAsync().ConfigureAwait(false);
  87. try
  88. {
  89. foreach (var session in _sessions)
  90. {
  91. await session.Value.StopAsync().ConfigureAwait(false);
  92. }
  93. _sessions.Clear();
  94. }
  95. finally
  96. {
  97. _semaphore.Release();
  98. }
  99. }
  100. public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
  101. {
  102. await _semaphore.WaitAsync().ConfigureAwait(false);
  103. try
  104. {
  105. return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
  106. {
  107. ClientId = s.Value.ClientId,
  108. ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311,
  109. LastPacketReceived = s.Value.LastPacketReceived,
  110. LastNonKeepAlivePacketReceived = s.Value.LastNonKeepAlivePacketReceived
  111. }).ToList();
  112. }
  113. finally
  114. {
  115. _semaphore.Release();
  116. }
  117. }
  118. public async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
  119. {
  120. try
  121. {
  122. applicationMessage = InterceptApplicationMessage(applicationMessage);
  123. if (applicationMessage == null)
  124. {
  125. return;
  126. }
  127. if (applicationMessage.Retain)
  128. {
  129. await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
  130. }
  131. _server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage);
  132. }
  133. catch (Exception exception)
  134. {
  135. _logger.Error<MqttClientSessionsManager>(exception, "Error while processing application message");
  136. }
  137. await _semaphore.WaitAsync().ConfigureAwait(false);
  138. try
  139. {
  140. foreach (var clientSession in _sessions.Values)
  141. {
  142. await clientSession.EnqueueApplicationMessageAsync(applicationMessage);
  143. }
  144. }
  145. finally
  146. {
  147. _semaphore.Release();
  148. }
  149. }
  150. private MqttApplicationMessage InterceptApplicationMessage(MqttApplicationMessage applicationMessage)
  151. {
  152. if (_options.ApplicationMessageInterceptor == null)
  153. {
  154. return applicationMessage;
  155. }
  156. var interceptorContext = new MqttApplicationMessageInterceptorContext
  157. {
  158. ApplicationMessage = applicationMessage
  159. };
  160. _options.ApplicationMessageInterceptor(interceptorContext);
  161. return interceptorContext.ApplicationMessage;
  162. }
  163. private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
  164. {
  165. if (_options.ConnectionValidator == null)
  166. {
  167. return MqttConnectReturnCode.ConnectionAccepted;
  168. }
  169. var context = new MqttConnectionValidatorContext(
  170. connectPacket.ClientId,
  171. connectPacket.Username,
  172. connectPacket.Password,
  173. connectPacket.WillMessage);
  174. _options.ConnectionValidator(context);
  175. return context.ReturnCode;
  176. }
  177. private async Task<GetOrCreateClientSessionResult> GetOrCreateClientSessionAsync(MqttConnectPacket connectPacket)
  178. {
  179. await _semaphore.WaitAsync().ConfigureAwait(false);
  180. try
  181. {
  182. var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
  183. if (isSessionPresent)
  184. {
  185. if (connectPacket.CleanSession)
  186. {
  187. _sessions.Remove(connectPacket.ClientId);
  188. await clientSession.StopAsync().ConfigureAwait(false);
  189. clientSession.Dispose();
  190. clientSession = null;
  191. _logger.Trace<MqttClientSessionsManager>("Stopped existing session of client '{0}'.", connectPacket.ClientId);
  192. }
  193. else
  194. {
  195. _logger.Trace<MqttClientSessionsManager>("Reusing existing session of client '{0}'.", connectPacket.ClientId);
  196. }
  197. }
  198. var isExistingSession = true;
  199. if (clientSession == null)
  200. {
  201. isExistingSession = false;
  202. clientSession = new MqttClientSession(connectPacket.ClientId, _options, _retainedMessagesManager, this, _logger);
  203. _sessions[connectPacket.ClientId] = clientSession;
  204. _logger.Trace<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId);
  205. }
  206. return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
  207. }
  208. finally
  209. {
  210. _semaphore.Release();
  211. }
  212. }
  213. }
  214. }