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.
 
 
 
 

135 lines
4.5 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.Diagnostics;
  8. using MQTTnet.Core.Exceptions;
  9. using MQTTnet.Core.Internal;
  10. using MQTTnet.Core.Packets;
  11. namespace MQTTnet.Core.Server
  12. {
  13. public sealed class MqttClientMessageQueue
  14. {
  15. private readonly List<MqttClientPublishPacketContext> _pendingPublishPackets = new List<MqttClientPublishPacketContext>();
  16. private readonly AsyncGate _gate = new AsyncGate();
  17. private readonly MqttServerOptions _options;
  18. private CancellationTokenSource _cancellationTokenSource;
  19. private IMqttCommunicationAdapter _adapter;
  20. public MqttClientMessageQueue(MqttServerOptions options)
  21. {
  22. _options = options ?? throw new ArgumentNullException(nameof(options));
  23. }
  24. public void Start(IMqttCommunicationAdapter adapter)
  25. {
  26. if (_cancellationTokenSource != null)
  27. {
  28. throw new InvalidOperationException($"{nameof(MqttClientMessageQueue)} already started.");
  29. }
  30. _adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
  31. _cancellationTokenSource = new CancellationTokenSource();
  32. Task.Run(() => SendPendingPublishPacketsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
  33. }
  34. public void Stop()
  35. {
  36. _adapter = null;
  37. _cancellationTokenSource?.Cancel();
  38. _cancellationTokenSource = null;
  39. }
  40. public void Enqueue(MqttPublishPacket publishPacket)
  41. {
  42. if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
  43. lock (_pendingPublishPackets)
  44. {
  45. _pendingPublishPackets.Add(new MqttClientPublishPacketContext(publishPacket));
  46. _gate.Set();
  47. }
  48. }
  49. private async Task SendPendingPublishPacketsAsync(CancellationToken cancellationToken)
  50. {
  51. while (!cancellationToken.IsCancellationRequested)
  52. {
  53. try
  54. {
  55. await _gate.WaitOneAsync().ConfigureAwait(false);
  56. if (cancellationToken.IsCancellationRequested)
  57. {
  58. return;
  59. }
  60. if (_adapter == null)
  61. {
  62. continue;
  63. }
  64. List<MqttClientPublishPacketContext> pendingPublishPackets;
  65. lock (_pendingPublishPackets)
  66. {
  67. pendingPublishPackets = _pendingPublishPackets.ToList();
  68. }
  69. foreach (var publishPacket in pendingPublishPackets)
  70. {
  71. await TrySendPendingPublishPacketAsync(publishPacket).ConfigureAwait(false);
  72. }
  73. }
  74. catch (Exception e)
  75. {
  76. MqttTrace.Error(nameof(MqttClientMessageQueue), e, "Error while sending pending publish packets.");
  77. }
  78. finally
  79. {
  80. Cleanup();
  81. }
  82. }
  83. }
  84. private async Task TrySendPendingPublishPacketAsync(MqttClientPublishPacketContext publishPacketContext)
  85. {
  86. try
  87. {
  88. if (_adapter == null)
  89. {
  90. return;
  91. }
  92. publishPacketContext.PublishPacket.Dup = publishPacketContext.SendTries > 0;
  93. await _adapter.SendPacketAsync(publishPacketContext.PublishPacket, _options.DefaultCommunicationTimeout).ConfigureAwait(false);
  94. publishPacketContext.IsSent = true;
  95. }
  96. catch (MqttCommunicationException exception)
  97. {
  98. MqttTrace.Warning(nameof(MqttClientMessageQueue), exception, "Sending publish packet failed.");
  99. }
  100. catch (Exception exception)
  101. {
  102. MqttTrace.Error(nameof(MqttClientMessageQueue), exception, "Sending publish packet failed.");
  103. }
  104. finally
  105. {
  106. publishPacketContext.SendTries++;
  107. }
  108. }
  109. private void Cleanup()
  110. {
  111. lock (_pendingPublishPackets)
  112. {
  113. _pendingPublishPackets.RemoveAll(p => p.IsSent);
  114. }
  115. }
  116. }
  117. }