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.
 
 
 
 

92 lines
3.2 KiB

  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Text;
  4. using System.Threading.Tasks;
  5. using MQTTnet.Client;
  6. using MQTTnet.Internal;
  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);
  22. }
  23. public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
  24. {
  25. if (methodName == null) throw new ArgumentNullException(nameof(methodName));
  26. if (methodName.Contains("/") || methodName.Contains("+") || methodName.Contains("#"))
  27. {
  28. throw new ArgumentException("The method name cannot contain /, + or #.");
  29. }
  30. var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}";
  31. var responseTopic = requestTopic + "/response";
  32. var requestMessage = new MqttApplicationMessageBuilder()
  33. .WithTopic(requestTopic)
  34. .WithPayload(payload)
  35. .WithQualityOfServiceLevel(qualityOfServiceLevel)
  36. .Build();
  37. try
  38. {
  39. var tcs = new TaskCompletionSource<byte[]>();
  40. if (!_waitingCalls.TryAdd(responseTopic, tcs))
  41. {
  42. throw new InvalidOperationException();
  43. }
  44. await _mqttClient.SubscribeAsync(responseTopic, qualityOfServiceLevel).ConfigureAwait(false);
  45. await _mqttClient.PublishAsync(requestMessage).ConfigureAwait(false);
  46. return await tcs.Task.TimeoutAfter(timeout);
  47. }
  48. finally
  49. {
  50. _waitingCalls.TryRemove(responseTopic, out _);
  51. await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
  52. }
  53. }
  54. private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
  55. {
  56. if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var tcs))
  57. {
  58. return;
  59. }
  60. if (tcs.Task.IsCompleted || tcs.Task.IsCanceled)
  61. {
  62. return;
  63. }
  64. tcs.TrySetResult(eventArgs.ApplicationMessage.Payload);
  65. }
  66. public void Dispose()
  67. {
  68. foreach (var tcs in _waitingCalls)
  69. {
  70. tcs.Value.SetCanceled();
  71. }
  72. _waitingCalls.Clear();
  73. }
  74. }
  75. }