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.

ManagedMqttClient.cs 13 KiB

пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
пре 7 година
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using MQTTnet.Client;
  8. using MQTTnet.Diagnostics;
  9. using MQTTnet.Exceptions;
  10. using MQTTnet.Protocol;
  11. namespace MQTTnet.ManagedClient
  12. {
  13. public class ManagedMqttClient : IManagedMqttClient
  14. {
  15. private readonly BlockingCollection<MqttApplicationMessage> _messageQueue = new BlockingCollection<MqttApplicationMessage>();
  16. private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
  17. private readonly SemaphoreSlim _subscriptionsSemaphore = new SemaphoreSlim(1, 1);
  18. private readonly IMqttClient _mqttClient;
  19. private readonly IMqttNetLogger _logger;
  20. private CancellationTokenSource _connectionCancellationToken;
  21. private CancellationTokenSource _publishingCancellationToken;
  22. private ManagedMqttClientStorageManager _storageManager;
  23. private IManagedMqttClientOptions _options;
  24. private bool _subscriptionsNotPushed;
  25. public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
  26. {
  27. _logger = logger ?? throw new ArgumentNullException(nameof(logger));
  28. _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
  29. _mqttClient.Connected += OnConnected;
  30. _mqttClient.Disconnected += OnDisconnected;
  31. _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
  32. }
  33. public bool IsConnected => _mqttClient.IsConnected;
  34. public event EventHandler<MqttClientConnectedEventArgs> Connected;
  35. public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
  36. public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
  37. public async Task StartAsync(IManagedMqttClientOptions options)
  38. {
  39. if (options == null) throw new ArgumentNullException(nameof(options));
  40. if (options.ClientOptions == null) throw new ArgumentException("The client options are not set.", nameof(options));
  41. if (!options.ClientOptions.CleanSession)
  42. {
  43. throw new NotSupportedException("The managed client does not support existing sessions.");
  44. }
  45. if (_connectionCancellationToken != null) throw new InvalidOperationException("The managed client is already started.");
  46. _options = options;
  47. if (_options.Storage != null)
  48. {
  49. _storageManager = new ManagedMqttClientStorageManager(_options.Storage);
  50. await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false);
  51. }
  52. _connectionCancellationToken = new CancellationTokenSource();
  53. #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
  54. Task.Run(async () => await MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token).ConfigureAwait(false);
  55. #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
  56. _logger.Info<ManagedMqttClient>("Started");
  57. }
  58. public Task StopAsync()
  59. {
  60. StopPublishing();
  61. StopMaintainingConnection();
  62. while (_messageQueue.Any())
  63. {
  64. _messageQueue.Take();
  65. }
  66. return Task.FromResult(0);
  67. }
  68. public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
  69. {
  70. if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
  71. foreach (var applicationMessage in applicationMessages)
  72. {
  73. if (_storageManager != null)
  74. {
  75. await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
  76. }
  77. _messageQueue.Add(applicationMessage);
  78. }
  79. }
  80. public async Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
  81. {
  82. if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
  83. await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
  84. try
  85. {
  86. foreach (var topicFilter in topicFilters)
  87. {
  88. _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
  89. _subscriptionsNotPushed = true;
  90. }
  91. }
  92. finally
  93. {
  94. _subscriptionsSemaphore.Release();
  95. }
  96. }
  97. public async Task UnsubscribeAsync(IEnumerable<string> topics)
  98. {
  99. await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
  100. try
  101. {
  102. foreach (var topic in topics)
  103. {
  104. if (_subscriptions.Remove(topic))
  105. {
  106. _subscriptionsNotPushed = true;
  107. }
  108. }
  109. }
  110. finally
  111. {
  112. _subscriptionsSemaphore.Release();
  113. }
  114. }
  115. public void Dispose()
  116. {
  117. _messageQueue?.Dispose();
  118. _subscriptionsSemaphore?.Dispose();
  119. _connectionCancellationToken?.Dispose();
  120. _publishingCancellationToken?.Dispose();
  121. }
  122. private async Task MaintainConnectionAsync(CancellationToken cancellationToken)
  123. {
  124. try
  125. {
  126. while (!cancellationToken.IsCancellationRequested)
  127. {
  128. await TryMaintainConnectionAsync(cancellationToken);
  129. }
  130. }
  131. catch (OperationCanceledException)
  132. {
  133. }
  134. catch (Exception exception)
  135. {
  136. _logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection.");
  137. }
  138. finally
  139. {
  140. await _mqttClient.DisconnectAsync().ConfigureAwait(false);
  141. _logger.Info<ManagedMqttClient>("Stopped");
  142. }
  143. }
  144. private async Task TryMaintainConnectionAsync(CancellationToken cancellationToken)
  145. {
  146. try
  147. {
  148. var connectionState = await ReconnectIfRequiredAsync().ConfigureAwait(false);
  149. if (connectionState == ReconnectionResult.NotConnected)
  150. {
  151. StopPublishing();
  152. await Task.Delay(_options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
  153. return;
  154. }
  155. if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
  156. {
  157. await PushSubscriptionsAsync().ConfigureAwait(false);
  158. StartPublishing();
  159. return;
  160. }
  161. if (connectionState == ReconnectionResult.StillConnected)
  162. {
  163. await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
  164. }
  165. }
  166. catch (OperationCanceledException)
  167. {
  168. }
  169. catch (MqttCommunicationException exception)
  170. {
  171. _logger.Warning<ManagedMqttClient>(exception, "Communication exception while maintaining connection.");
  172. }
  173. catch (Exception exception)
  174. {
  175. _logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection.");
  176. }
  177. }
  178. private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
  179. {
  180. try
  181. {
  182. while (!cancellationToken.IsCancellationRequested)
  183. {
  184. var message = _messageQueue.Take(cancellationToken);
  185. if (message == null)
  186. {
  187. continue;
  188. }
  189. if (cancellationToken.IsCancellationRequested)
  190. {
  191. continue;
  192. }
  193. await TryPublishQueuedMessageAsync(message).ConfigureAwait(false);
  194. }
  195. }
  196. catch (OperationCanceledException)
  197. {
  198. }
  199. catch (Exception exception)
  200. {
  201. _logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application messages.");
  202. }
  203. finally
  204. {
  205. _logger.Trace<ManagedMqttClient>("Stopped publishing messages.");
  206. }
  207. }
  208. private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message)
  209. {
  210. try
  211. {
  212. await _mqttClient.PublishAsync(message).ConfigureAwait(false);
  213. if (_storageManager != null)
  214. {
  215. await _storageManager.RemoveAsync(message).ConfigureAwait(false);
  216. }
  217. }
  218. catch (MqttCommunicationException exception)
  219. {
  220. _logger.Warning<ManagedMqttClient>(exception, "Publishing application message failed.");
  221. if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
  222. {
  223. _messageQueue.Add(message);
  224. }
  225. }
  226. catch (Exception exception)
  227. {
  228. _logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application message.");
  229. }
  230. }
  231. private async Task PushSubscriptionsAsync()
  232. {
  233. _logger.Info<ManagedMqttClient>(nameof(ManagedMqttClient), "Synchronizing subscriptions");
  234. List<TopicFilter> subscriptions;
  235. await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
  236. try
  237. {
  238. subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList();
  239. _subscriptionsNotPushed = false;
  240. }
  241. finally
  242. {
  243. _subscriptionsSemaphore.Release();
  244. }
  245. if (!subscriptions.Any())
  246. {
  247. return;
  248. }
  249. try
  250. {
  251. await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
  252. }
  253. catch (Exception exception)
  254. {
  255. _logger.Warning<ManagedMqttClient>(exception, "Synchronizing subscriptions failed");
  256. _subscriptionsNotPushed = true;
  257. }
  258. }
  259. private async Task<ReconnectionResult> ReconnectIfRequiredAsync()
  260. {
  261. if (_mqttClient.IsConnected)
  262. {
  263. return ReconnectionResult.StillConnected;
  264. }
  265. try
  266. {
  267. await _mqttClient.ConnectAsync(_options.ClientOptions).ConfigureAwait(false);
  268. return ReconnectionResult.Reconnected;
  269. }
  270. catch (Exception)
  271. {
  272. return ReconnectionResult.NotConnected;
  273. }
  274. }
  275. private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
  276. {
  277. ApplicationMessageReceived?.Invoke(this, eventArgs);
  278. }
  279. private void OnDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs)
  280. {
  281. Disconnected?.Invoke(this, eventArgs);
  282. }
  283. private void OnConnected(object sender, MqttClientConnectedEventArgs eventArgs)
  284. {
  285. Connected?.Invoke(this, eventArgs);
  286. }
  287. private void StartPublishing()
  288. {
  289. if (_publishingCancellationToken != null)
  290. {
  291. StopPublishing();
  292. }
  293. var cts = new CancellationTokenSource();
  294. _publishingCancellationToken = cts;
  295. #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
  296. Task.Run(async () => await PublishQueuedMessagesAsync(cts.Token).ConfigureAwait(false), cts.Token).ConfigureAwait(false);
  297. #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
  298. }
  299. private void StopPublishing()
  300. {
  301. _publishingCancellationToken?.Cancel(false);
  302. _publishingCancellationToken?.Dispose();
  303. _publishingCancellationToken = null;
  304. }
  305. private void StopMaintainingConnection()
  306. {
  307. _connectionCancellationToken?.Cancel(false);
  308. _connectionCancellationToken?.Dispose();
  309. _connectionCancellationToken = null;
  310. }
  311. }
  312. }