我正在使用官方文档学习gRPC,但发现客户端流和双向流的方法签名非常混乱(两者是相同的)。
从这里的文档来看,该函数StreamObserver<ResponseType>作为输入参数并返回一个StreamObserver<ResponseType>实例,如下所示:
public StreamObserver<RequestType> bidirectionalStreamingExample(
StreamObserver<ResponseType> responseObserver)
Run Code Online (Sandbox Code Playgroud)
但在我看来,它应该将RequestType类型作为输入并返回ResponseType类型:
public StreamObserver<ResponseType> bidirectionalStreamingExample(
StreamObserver<RequestType> responseObserver)
Run Code Online (Sandbox Code Playgroud)
这让我很困惑,实际上我有点惊讶的是,当我在谷歌搜索时,答案没有提示,我以为很多人都会有同样的问题。我在这里遗漏了一些明显的东西吗?为什么 gRPC 会这样定义签名?
您的困惑可能源于使用 REST 或非流框架,其中请求-响应通常映射到函数的参数-返回。这里的范式转变是您不再提供请求-响应,而是提供删除请求和响应的渠道。如果您学过 C 或 C++,那么这很像从
int get_square_root(int input);
Run Code Online (Sandbox Code Playgroud)
到
void get_square_root(int input, int& output);
Run Code Online (Sandbox Code Playgroud)
看看output现在的参数怎么样?但如果这根本没有意义(我的错:-),这里有一个更有机的路径:
让我们从服务器流存根开始,即使您的最终目标是客户端流。
public void serverStreamingExample(
RequestType request,
StreamObserver<ResponseType> responseObserver)
Run Code Online (Sandbox Code Playgroud)
Q:为什么参数列表中有“response”?答:参数列表中的不是响应,而是提供最终响应的通道。例如:
public void serverStreamingExample(
RequestType request,
StreamObserver<ResponseType> responseObserver) {
ResponseType response = processRequest(request);
responseObserver.onNext(response); // this is the "return"
responseObserver.onCompleted();
}
Run Code Online (Sandbox Code Playgroud)
为什么?因为,流式传输的目的是保持响应可以持续流过的通道。如果您只能返回 1 个响应,并且函数已完成,那么它就不是流。通过提供一个渠道,您作为开发人员可以选择根据需要传递它,通过您想要的方式提供尽可能多的响应,onNext()直到您满意并致电为止onCompleted()。
现在,让我们继续讨论客户端流存根:
public StreamObserver<RequestType> clientStreamingExample(
StreamObserver<ResponseType> responseObserver)
Run Code Online (Sandbox Code Playgroud)
问:等等,什么!我们现在知道为什么响应位于参数列表中,但是返回请求有何意义呢?答:再说一次,我们实际上并不是返回一个请求,而是一个客户端删除请求的通道!为什么?因为客户端流的目的是允许客户端分块提供请求。它无法通过对服务器的一次传统调用来做到这一点。因此,这是一种实现方法:
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;
}
};
}
}
Run Code Online (Sandbox Code Playgroud)
您可能需要花一些时间研究这一点才能完全理解,但基本上,由于客户端现在可能发送请求流,因此您必须为每个请求片段定义处理程序,以及为客户端定义已完成或出错的处理程序出去。(在我的示例中,我让服务器仅在客户端表示已完成时才响应,但您可以自由地执行任何您想做的操作。您甚至可以让服务器在客户端表示已完成或根本不响应之前做出响应。)
这根本就不是个事啊!:-) 我的意思是,教程只是想指出,没有什么可以阻止您完全实现上述内容,只是在两侧。因此,您最终会得到 2 个应用程序,它们分块发送和接收请求,并发送和接收响应。他们将此设置称为双向流,他们的说法是正确的,但这只是有点误导,因为它在技术上与客户端流没有做任何不同的事情。这就是签名相同的原因。恕我直言,教程应该只提到我在这里的注释,而不是重复存根。
我们从 C++ 的类比开始:
int get_square_root(int input); // "traditional" request-response
Run Code Online (Sandbox Code Playgroud)
到
void get_square_root(int input, int& output); // server streaming
Run Code Online (Sandbox Code Playgroud)
我们想继续这个类比吗?我们当然知道。
你好, C++ 函数指针,我的老朋友……
void (*fnPtr)(int) get_square_root_fn(int& output); // client streaming
Run Code Online (Sandbox Code Playgroud)
以及它的用途(无用)的演示:
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
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
413 次 |
| 最近记录: |