gRPC推送和扇出

时间:2017-07-14 16:10:36

标签: .net grpc

是否可以使用gRPC作为具有扇出功能的推送服务? 在Google提供的示例中,服务器端(C#)有以下代码:

    public override async Task ListFeatures(Rectangle request, IServerStreamWriter<Feature> responseStream, ServerCallContext context)
    {
        var responses = features.FindAll( (feature) => feature.Exists() && request.Contains(feature.Location) );
        foreach (var response in responses)
        {
            await responseStream.WriteAsync(response);
        }
    }

问题在于:

  1. 只有在客户明确要求时才会生成和写入数据。
  2. 只有提出要求的客户才能获得新数据。
  3. 我认为我需要的是:

    1. 为每个要求(订阅)的客户端保留所有IServerStreamWriters。
    2. 当有新数据可用时触发外部事件的写入。
    3. 写入所有streamWriters
    4. 修改 根据Carl的建议,我现在有以下内容:

      原:

      service PubSub {
       rpc Subscribe(Subscription) returns (stream Event) {}
       rpc Unsubscribe(Subscription) returns (Unsubscription) {}
      }
      
      message Event
      {
         string Value = 1;
      }
      message Subscription
      {
        string Id = 1;
      }
      message Unsubscription
      {
        string Id = 1;
      }
      

      PubSubImpl:

      public class PubSubImpl : PubSub.PubSubBase
      {
          private readonly BufferBlock<Event> _buffer = new BufferBlock<Event>();
      
          private Dictionary<string, IServerStreamWriter<Event>> _subscriberWritersMap =
              new Dictionary<string, IServerStreamWriter<Event>>();
      
           public override async Task Subscribe(Subscription subscription, IServerStreamWriter<Event> responseStream, ServerCallContext context)
          {
              //Dict to hold a streamWriter for each subscriber.
              _subscriberWritersMap[subscription.Id] = responseStream;
      
              while (_subscriberWritersMap.ContainsKey(subscription.Id))
              {
                  //Wait on BufferBlock from MS Dataflow package.
                  var @event = await _buffer.ReceiveAsync();
                  foreach (var serverStreamWriter in _subscriberWritersMap.Values)
                  {
                      await serverStreamWriter.WriteAsync(@event);
                  }
              }
          }
      
          public override Task<Unsubscription> Unsubscribe(Subscription request, ServerCallContext context)
          {
              _subscriberWritersMap.Remove(request.Id);
              return Task.FromResult(new Unsubscription() { Id = request.Id });
          }
      
          public void Publish(string input)
          {
              _buffer.Post(new Event() { Value = input });
          }
      }
      

      &#34; Push&#34; s现在可以像这样发送:

         while ((input = Console.ReadLine()) != "q")
          {
            pubsubImp.Publish(input);
          }
      

      在客户端,我有:

      public async Task Subscribe()
      {
          _subscription = new Subscription() { Id = Guid.NewGuid().ToString() };
          using (var call = _pubSubClient.Subscribe(_subscription))
          {
              //Receive
              var responseReaderTask = Task.Run(async () =>
              {
                  while (await call.ResponseStream.MoveNext())
                  {
                      Console.WriteLine("Event received: " + call.ResponseStream.Current);
                  }
              });
      
              await responseReaderTask;
          }
      }
      
      public void Unsubscribe()
      {
          _pubSubClient.Unsubscribe(_subscription);
      }
      

      Client-Main的工作原理如下:

      static void Main(string[] args)
      {
          var channel = new Channel("127.0.0.1:50052", 
                                      ChannelCredentials.Insecure);
          var subscriber = new Subsriber(new PubSub.PubSubClient(channel));
      
          Task.Run(async () =>
          {
              await subscriber.Subscribe();
          }).GetAwaiter();
      
          Console.WriteLine("Hit key to unsubscribe");
          Console.ReadLine();
      
          subscriber.Unsubscribe();
      
          Console.WriteLine("Unsubscribed...");
      
          Console.WriteLine("Hit key to exit...");
          Console.ReadLine();
      
      }
      

      目前它看起来像是有效的。是应该/可以做到的? 测试解决方案可在以下位置找到 https://github.com/KingKnecht/gRPC-PubSub

2 个答案:

答案 0 :(得分:1)

虽然我不能评论它是否是一种好的做法,但这是可能的。您需要像所说的那样跟踪每个客户端,并确保在客户端断开连接时将其删除以使其无法访问。

您的一般方法听起来是正确的。推送RPC应该是双向流式传输(假设每个客户端都可以引起推送)。当客户端连接到服务器时,将客户端记录在线程安全集合中。当其中一个客户端发送消息时,迭代通过集合将消息发送到每个连接的客户端。如果发送失败,请从客户端删除客户端并关闭连接。

在名为&#34; RouteGuide&#34;的示例中有一个更简单的版本。它以gRPC支持的语言实现了一个简单的聊天服务器。

答案 1 :(得分:0)

我认为您不需要在服务器应用程序中跟踪客户端。相反,对于每个“推”方法,创建一个EventWaitHandle并继续等待它。然后在另一种情况下,向EventWaitHandle发信号,以便等待的“推”方法可以WriteAsync发送给客户端。

例如

public class PubSubImpl : PubSub.PubSubBase
{
    EventWaitHandle evwStatus = new EventWaitHandle(false, EventResetMode.ManualReset);
    string status;

    public override async Task Subscribe(Subscription subscription, IServerStreamWriter<Event> responseStream, ServerCallContext context)
    {
        // respond with the current status
        await serverStreamWriter.WriteAsync(new Event { Value = status });
        // wait until we're signaled with a different status
        while(evwStatus.WaitOne())
        {
            await serverStreamWriter.WriteAsync(new Event { Value = status });
        }
    }   

    public void Publish(string input)
    {
        status = input;
        // let the waiting threads respond
        evwStatus.Set();
        // halt the waiting threads
        evwStatus.Reset();
    }
}