grpc - 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 会这样定义签名?
解决方案
您的困惑可能源于习惯于 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
}
推荐阅读
- reactjs - 如何设置 react-native-sectioned-multi-select 样式?
- firebase - Firebase 函数 - admin.firestore() get() 在本地服务上引发身份验证错误
- r - 使用 dplyr vs base 过滤数据的不同结果
- android - 如何在 Android 上强制 chromium 发送凭据?
- java - 如何从列表中制作迭代器
> 收藏? - scala - 将元组值分配给全局变量
- python - 在 kivy 应用程序中实现加载弹出窗口的正确方法
- jenkins - jenkins 上的动态参数选择
- javascript - 无法从 Javascript 中的数组中获取数组
- elasticsearch - 弹性查询仅返回最大分数记录