首页 > 解决方案 > gRPC 双向函数返回 RequestType 而不是 ResponseType

问题描述

我正在使用官方文档学习gRPC,但发现client-streaming和bidirectional-streaming的方法签名非常混乱(两者相同)。

此处的文档中,该函数将StreamObserver<ResponseType>作为输入参数并返回一个StreamObserver<ResponseType>实例,如下所示:

public StreamObserver<RequestType> bidirectionalStreamingExample(
    StreamObserver<ResponseType> responseObserver)

但在我看来,它应该将RequestType类型作为输入并返回ResponseType类型:

public StreamObserver<ResponseType> bidirectionalStreamingExample(
    StreamObserver<RequestType> responseObserver)

这让我非常困惑,实际上我有点惊讶,当我在谷歌搜索时没有提示答案,我以为很多人都会有同样的问题。我在这里遗漏了一些明显的东西吗?为什么 gRPC 会这样定义签名?

标签: grpcgrpc-java

解决方案


您的困惑可能源于习惯于 REST 或非流式框架,其中请求-响应通常映射到函数的参数返回。这里的范式转变是您不再提供请求-响应,而是提供丢弃请求和响应的渠道。如果你学过 C 或 C++,这很像从

int get_square_root(int input);

void get_square_root(int input, int& output);

看看output现在的参数如何?但万一这根本没有意义(我的错:-)这是一条更有机的路径:

服务器流式传输

让我们从服务器流存根开始,即使您的最终目标是客户端流。

public void serverStreamingExample(
    RequestType request,
    StreamObserver<ResponseType> responseObserver)

Q:为什么参数列表中有“response”?答:不是参数列表中的响应,而是将最终响应提供给的通道。例如:

public void serverStreamingExample(
    RequestType request,
    StreamObserver<ResponseType> responseObserver) {
  
  ResponseType response = processRequest(request);

  responseObserver.onNext(response); // this is the "return"
  responseObserver.onCompleted();
}

为什么?因为,流式传输的目的是让响应可以继续通过的通道保持活动状态。如果您只能返回 1 个响应,仅此而已,则该函数已完成,那么这不是流。通过提供一个通道,作为开发人员,您可以选择根据需要传递它,并根据需要向它提供尽可能多的响应,onNext()直到您满意并致电onCompleted().

客户端流式传输

现在,让我们继续讨论客户端流存根:

public StreamObserver<RequestType> clientStreamingExample(
    StreamObserver<ResponseType> responseObserver)

问:等等,什么!我们现在知道为什么响应在参数列表中,但是返回请求有什么意义呢?A:同样,我们实际上并没有返回请求,而是客户端丢弃请求的通道!为什么?因为客户端流式传输的目的是允许客户端分段提供请求。它不能通过对服务器的单一传统调用来做到这一点。因此,这是可以实现的一种方法:

class ClientStreamingExample {

  int piecesRcvd = 0;

  public StreamObserver<RequestType> myClientStreamingEndpoint(
      StreamObserver<ResponseType> responseObserver) {

    return new StreamObserver<RequestType>() {

      @Override
      public void onNext(RequestType requestPiece) {
        // do whatever you want with the request pieces
        piecesRcvd++;
      }

      @Override
      public void onCompleted() {
        // when the client says they're done sending request pieces,
        // send them a response back (but you don't have to! or it can
        // be conditional!)
        ResponseType response =
            new ResponseType("received " + piecesRcvd + " pieces");
        responseObserver.onNext(response);
        responseObserver.onCompleted();
        piecesRcvd = 0;
      }

      @Override
      public void onError() {
        piecesRcvd = 0;
      }
    };
  }
}

您可能需要花一点时间来研究它才能完全理解,但基本上,由于客户端现在可以发送请求流,您必须为每个请求块定义处理程序,以及客户端的处理程序说它已完成或出错出去。(在我的示例中,我让服务器仅在客户端说已完成时响应,但您可以自由地做任何您想做的事情。您甚至可以在客户端说它已完成或根本不响应之前让服务器响应。

双向流

这真的不是一回事!:-) 我的意思是,教程只是要指出,没有什么能阻止你完全实现上述内容,只是在双方。因此,您最终会得到 2 个应用程序,它们分段发送和接收请求,并发送和接收响应。他们将此设置称为双向流,他们是正确的,但这只是有点误导,因为它在技术上与客户端流没有任何不同。这正是签名相同的原因。恕我直言,教程应该只提到我在这里的注释,而不是重复存根。

可选:只是为了“好玩”...

我们从 C++ 类比开始

int get_square_root(int input); // "traditional" request-response

void get_square_root(int input, int& output); // server streaming

我们要继续这个类比吗?我们当然知道。

你好, C++ 函数指针,我的老朋友……

void (*fnPtr)(int) get_square_root_fn(int& output); // client streaming

并展示了它的使用(少):

int main() { // aka the client
  int result;
  void (*fnPtr)(int) = server.get_square_root_fn(result);
  fnPtr(2);
  std::cout << result << std::endl; // 1.4142 assuming the fn actually does sqrt
}

推荐阅读