Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 

115 рядки
4.3 KiB

  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Text;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using MQTTnet.Client;
  7. using MQTTnet.Protocol;
  8. namespace MQTTnet.Extensions.Rpc
  9. {
  10. public sealed class MqttRpcClient : IDisposable
  11. {
  12. private readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
  13. private readonly IMqttClient _mqttClient;
  14. public MqttRpcClient(IMqttClient mqttClient)
  15. {
  16. _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
  17. _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
  18. }
  19. public Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
  20. {
  21. return ExecuteAsync(timeout, methodName, Encoding.UTF8.GetBytes(payload), qualityOfServiceLevel, CancellationToken.None);
  22. }
  23. public Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken)
  24. {
  25. return ExecuteAsync(timeout, methodName, Encoding.UTF8.GetBytes(payload), qualityOfServiceLevel, cancellationToken);
  26. }
  27. public Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
  28. {
  29. return ExecuteAsync(timeout, methodName, payload, qualityOfServiceLevel, CancellationToken.None);
  30. }
  31. public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken)
  32. {
  33. if (methodName == null) throw new ArgumentNullException(nameof(methodName));
  34. if (methodName.Contains("/") || methodName.Contains("+") || methodName.Contains("#"))
  35. {
  36. throw new ArgumentException("The method name cannot contain /, + or #.");
  37. }
  38. var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}";
  39. var responseTopic = requestTopic + "/response";
  40. var requestMessage = new MqttApplicationMessageBuilder()
  41. .WithTopic(requestTopic)
  42. .WithPayload(payload)
  43. .WithQualityOfServiceLevel(qualityOfServiceLevel)
  44. .Build();
  45. try
  46. {
  47. var tcs = new TaskCompletionSource<byte[]>();
  48. if (!_waitingCalls.TryAdd(responseTopic, tcs))
  49. {
  50. throw new InvalidOperationException();
  51. }
  52. await _mqttClient.SubscribeAsync(responseTopic, qualityOfServiceLevel).ConfigureAwait(false);
  53. await _mqttClient.PublishAsync(requestMessage).ConfigureAwait(false);
  54. using (var timeoutCts = new CancellationTokenSource(timeout))
  55. {
  56. timeoutCts.Token.Register(() =>
  57. {
  58. if (!tcs.Task.IsCompleted && !tcs.Task.IsFaulted && !tcs.Task.IsCanceled)
  59. {
  60. tcs.TrySetCanceled();
  61. }
  62. });
  63. var result = await tcs.Task.ConfigureAwait(false);
  64. timeoutCts.Cancel(false);
  65. return result;
  66. }
  67. }
  68. finally
  69. {
  70. _waitingCalls.TryRemove(responseTopic, out _);
  71. await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
  72. }
  73. }
  74. private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
  75. {
  76. if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var tcs))
  77. {
  78. return;
  79. }
  80. if (tcs.Task.IsCompleted || tcs.Task.IsCanceled)
  81. {
  82. return;
  83. }
  84. tcs.TrySetResult(eventArgs.ApplicationMessage.Payload);
  85. }
  86. public void Dispose()
  87. {
  88. foreach (var tcs in _waitingCalls)
  89. {
  90. tcs.Value.SetCanceled();
  91. }
  92. _waitingCalls.Clear();
  93. }
  94. }
  95. }