gRPC 是一个高性能、开源的远程过程调用(RPC)框架,由 Google 开发。它旨在提供跨语言的通信能力,适用于从移动设备到数据中心服务器的各种环境。
1. 核心概念
Protocol Buffers(protobuf):
- gRPC 使用 Protocol Buffers 作为其接口描述语言和数据序列化协议。开发者通过定义
.proto
文件来指定服务和消息格式。Protocol Buffers 提供了一种紧凑的二进制格式,支持快速序列化和反序列化。
- gRPC 使用 Protocol Buffers 作为其接口描述语言和数据序列化协议。开发者通过定义
HTTP/2 协议:
- gRPC 基于 HTTP/2 进行通信。HTTP/2 提供了多路复用、头部压缩、流控制和双向流等特性,显著提高了传输效率和性能。
服务定义和代码生成:
- 开发者在
.proto
文件中定义服务和 RPC 方法。gRPC 编译器protoc
根据这些定义生成客户端和服务器端的代码存根,简化了开发工作。
- 开发者在
多种通信模式:
gRPC 支持四种服务方法类型,这使得 gRPC 可以适应多种应用场景:- 单一响应 RPC
- 服务器流式 RPC
- 客户端流式 RPC
- 双向流式 RPC
安全性:
- gRPC 支持 TLS/SSL 加密,提供安全的通信通道。它还支持多种身份验证机制。
2. 实现原理
- 服务定义和编译
定义服务:在
.proto
文件中定义服务接口和消息类型。syntax = "proto3"; service Greeter { rpc SayHello (HelloRequest) returns (HelloResponse); } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; }
- 编译生成代码:使用
protoc
编译器生成客户端和服务器代码。这些代码包含了与服务交互所需的存根和骨架。
- 客户端和服务器的实现
服务器端:实现生成的服务接口,定义具体的业务逻辑。启动 gRPC 服务器,注册服务。
public class GreeterImpl extends GreeterGrpc.GreeterImplBase { @Override public void sayHello(HelloRequest req, StreamObserver<HelloResponse> responseObserver) { HelloResponse response = HelloResponse.newBuilder() .setMessage("Hello, " + req.getName()) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } }
客户端:使用生成的客户端存根与服务器交互。客户端可以选择同步或异步调用。
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051) .usePlaintext() .build(); GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel); HelloResponse response = stub.sayHello(HelloRequest.newBuilder().setName("World").build());
- 传输层:基于 Netty 的实现
- Netty 集成:gRPC 的 Java 实现使用 Netty 作为传输层框架。Netty 提供了异步事件驱动的 I/O 模型,支持高效的网络通信。
- HTTP/2 支持:Netty 提供对 HTTP/2 的支持,使得 gRPC 可以利用 HTTP/2 的特性,如多路复用和头部压缩。
- 数据序列化和反序列化
- gRPC 使用 Protocol Buffers 进行消息的序列化和反序列化。Protocol Buffers 提供了高效的二进制格式,减少了数据传输的开销。
- 拦截器和中间件
- gRPC 支持拦截器机制,允许在请求和响应的生命周期中插入自定义逻辑。这类似于中间件,可以用于实现日志记录、认证、审计等功能。
- 负载均衡和服务发现
- gRPC 提供了多种负载均衡策略,并且可以与服务发现机制集成。客户端可以自动选择最佳的服务器实例来处理请求。
3. 代码示例
要在 Java 项目中使用 gRPC,你需要设置 Maven 项目并添加必要的依赖项,然后编写服务和客户端代码。以下是一个简单的示例,展示如何在 Java 中使用 gRPC。
3.1. 设置 Maven 项目
首先,确保你的 pom.xml
文件包含以下 gRPC 和 Protocol Buffers 相关的依赖和插件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>grpc-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<grpc.version>1.56.0</grpc.version>
<protobuf.version>3.21.12</protobuf.version>
<os.detected.classifier>osx-x86_64</os.detected.classifier>
</properties>
<dependencies>
<!-- gRPC dependencies -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<!-- Protocol Buffers -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!-- Protobuf Maven Plugin -->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- OS Maven Plugin -->
<plugin>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
</plugin>
</plugins>
</build>
</project>
os.detected.classifier
与使用的系统有关
3.2. 定义 Protocol Buffers 文件
首先,定义 .proto
文件以支持各种类型的流:
在 src/main/proto
目录下创建一个文件 hello.proto
:
syntax = "proto3";
option java_package = "com.example.grpc";
option java_outer_classname = "HelloProto";
service Greeter {
// 单一响应
rpc SayHello (HelloRequest) returns (HelloReply) {}
// 客户端流
rpc SayHelloClientStream (stream HelloRequest) returns (HelloReply) {}
// 服务器流
rpc SayHelloServerStream (HelloRequest) returns (stream HelloReply) {}
// 双向流
rpc SayHelloBidirectionalStream (stream HelloRequest) returns (stream HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
基于 .proto 生成 Java 文件
在命令行中运行以下命令来生成 Java 类:
mvn clean compile
这将使用 protobuf-maven-plugin 生成 gRPC 服务和消息类。
生成类在 /target
目录下,即 /target/generated-sources/protobuf/grpc-java/org/example/grpc
3.3. 实现 gRPC 服务
在服务端实现这些不同类型的流,同时使用拦截器来处理请求头:
import io.grpc.*;
import io.grpc.stub.StreamObserver;
import io.netty.util.internal.StringUtil;
import org.example.grpc.interceptor.ServerHeaderInterceptor;
import org.example.grpc.interceptor.ServerLoggingInterceptor;
import java.io.IOException;
import java.util.logging.Logger;
public class HelloServer {
private static final Logger logger = Logger.getLogger(HelloServer.class.getName());
private Server server;
public static void main(String[] args) throws IOException, InterruptedException {
final HelloServer server = new HelloServer();
server.start();
server.blockUntilShutdown();
}
private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.intercept(new ServerHeaderInterceptor()) // 添加拦截器
.intercept(new ServerLoggingInterceptor())
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloServer.this.stop();
System.err.println("*** server shut down");
}));
}
private void stop() {
if (server != null) {
server.shutdown();
}
}
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
/**
* 单一响应
*/
@Override
public void sayHello(HelloProto.HelloRequest req, StreamObserver<HelloProto.HelloReply> responseObserver) {
System.out.println("【单一响应】收到消息 - " + req.getName());
HelloProto.HelloReply reply = HelloProto.HelloReply.newBuilder()
.setMessage("【单一响应】Hello " + req.getName())
.build();
System.out.println("【单一响应】响应消息 - " + req.getName());
responseObserver.onNext(reply);
System.out.println("【单一响应】服务端发起关闭");
responseObserver.onCompleted();
}
/**
* 客户端流
*/
@Override
public StreamObserver<HelloProto.HelloRequest> sayHelloClientStream(final StreamObserver<HelloProto.HelloReply> responseObserver) {
return new StreamObserver<HelloProto.HelloRequest>() {
StringBuilder names = new StringBuilder();
@Override
public void onNext(HelloProto.HelloRequest request) {
if (names.length() > 0) {
names.append(", ");
}
System.out.println("【客户端流】收到消息 - " + request.getName());
names.append(request.getName());
}
@Override
public void onError(Throwable t) {
logger.warning("Error in client stream: " + t);
}
@Override
public void onCompleted() {
System.out.println("【客户端流】收到客户端已关闭消息");
HelloProto.HelloReply reply = HelloProto.HelloReply.newBuilder()
.setMessage("【客户端流】Hello " + names.toString())
.build();
System.out.println("【客户端流】响应消息 - " + names.toString());
responseObserver.onNext(reply);
System.out.println("【客户端流】服务端发起关闭");
responseObserver.onCompleted();
}
};
}
/**
* 服务端流
*/
@Override
public void sayHelloServerStream(HelloProto.HelloRequest req, StreamObserver<HelloProto.HelloReply> responseObserver) {
for (int i = 0; i < 5; i++) {
String msg = req.getName() + StringUtil.SPACE + i;
HelloProto.HelloReply reply = HelloProto.HelloReply.newBuilder()
.setMessage("【服务端流】Hello " + msg)
.build();
System.out.println("【服务端流】响应消息 - " + msg);
responseObserver.onNext(reply);
}
System.out.println("【服务端流】服务端发起关闭");
responseObserver.onCompleted();
}
/**
* 双向流
*/
@Override
public StreamObserver<HelloProto.HelloRequest> sayHelloBidirectionalStream(final StreamObserver<HelloProto.HelloReply> responseObserver) {
return new StreamObserver<HelloProto.HelloRequest>() {
@Override
public void onNext(HelloProto.HelloRequest request) {
HelloProto.HelloReply reply = HelloProto.HelloReply.newBuilder()
.setMessage("Hello " + request.getName())
.build();
System.out.println("【双向流】收到消息 - " + request.getName());
System.out.println("【双向流】回复消息 - " + request.getName());
responseObserver.onNext(reply);
}
@Override
public void onError(Throwable t) {
logger.warning("Error in bidirectional stream: " + t);
}
@Override
public void onCompleted() {
System.out.println("【双向流】收到客户端已关闭消息");
System.out.println("【双向流】服务端发起关闭");
responseObserver.onCompleted();
}
};
}
}
}
3.4. 实现 gRPC 客户端
编写客户端代码来调用这些不同类型的流:
抱歉,以下是完整的客户端代码示例,包括所有类型的流调用:
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
import org.example.grpc.interceptor.ClientLoggingInterceptor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class HelloClient {
private static final Logger logger = Logger.getLogger(HelloClient.class.getName());
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
private final GreeterGrpc.GreeterStub asyncStub;
public HelloClient(String host, int port) {
channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.intercept(new ClientLoggingInterceptor())
.build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
asyncStub = GreeterGrpc.newStub(channel);
}
public static void main(String[] args) throws Exception {
HelloClient client = new HelloClient("localhost", 50051);
try {
client.greet("world");
client.greetClientStream("Alice", "Bob", "Charlie");
client.greetServerStream("Dave");
client.greetBidirectionalStream("Eve", "Frank");
} finally {
client.shutdown();
}
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
/**
* 单一响应
*/
public void greet(String name) {
HelloProto.HelloRequest request = HelloProto.HelloRequest.newBuilder().setName(name).build();
HelloProto.HelloReply response;
try {
System.out.println("【单一响应】发送消息: " + name);
response = blockingStub.sayHello(request);
System.out.println("【单一响应】收到响应: " + response.getMessage());
} catch (Exception e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getMessage());
}
}
/**
* 客户端流
*/
public void greetClientStream(String... names) {
StreamObserver<HelloProto.HelloReply> responseObserver = new StreamObserver<HelloProto.HelloReply>() {
@Override
public void onNext(HelloProto.HelloReply value) {
System.out.println("【客户端流】收到响应: " + value.getMessage());
}
@Override
public void onError(Throwable t) {
logger.warning("【客户端流】Error in client stream: " + t);
}
@Override
public void onCompleted() {
System.out.println("【客户端流】收到服务端已关闭消息");
}
};
StreamObserver<HelloProto.HelloRequest> requestObserver = asyncStub.sayHelloClientStream(responseObserver);
for (String name : names) {
HelloProto.HelloRequest request = HelloProto.HelloRequest.newBuilder().setName(name).build();
System.out.println("【客户端流】发送消息 - " + name);
requestObserver.onNext(request);
}
System.out.println("【客户端流】客户端发起关闭");
requestObserver.onCompleted();
}
/**
* 服务端流
*/
public void greetServerStream(String name) {
HelloProto.HelloRequest request = HelloProto.HelloRequest.newBuilder().setName(name).build();
StreamObserver<HelloProto.HelloReply> responseObserver = new StreamObserver<HelloProto.HelloReply>() {
@Override
public void onNext(HelloProto.HelloReply value) {
System.out.println("【服务端流】收到响应: " + value.getMessage());
}
@Override
public void onError(Throwable t) {
logger.warning("【服务端流】Error in server stream: " + t);
}
@Override
public void onCompleted() {
System.out.println("【服务端流】收到服务端已关闭消息");
}
};
asyncStub.sayHelloServerStream(request, responseObserver);
}
/**
* 双向流
*/
public void greetBidirectionalStream(String... names) {
StreamObserver<HelloProto.HelloReply> responseObserver = new StreamObserver<HelloProto.HelloReply>() {
@Override
public void onNext(HelloProto.HelloReply value) {
System.out.println("【双向流】收到响应: " + value.getMessage());
}
@Override
public void onError(Throwable t) {
logger.warning("【双向流】Error in bidirectional stream: " + t);
}
@Override
public void onCompleted() {
System.out.println("【双向流】收到服务端已关闭消息");
}
};
StreamObserver<HelloProto.HelloRequest> requestObserver = asyncStub.sayHelloBidirectionalStream(responseObserver);
for (String name : names) {
HelloProto.HelloRequest request = HelloProto.HelloRequest.newBuilder().setName(name).build();
requestObserver.onNext(request);
}
System.out.println("【双向流】客户端发起关闭");
requestObserver.onCompleted();
}
}
3.5. 实现拦截器
gRPC 支持拦截器来处理请求头等。以下是一个简单的拦截器示例:
服务端拦截器 1
import io.grpc.*;
public class ServerHeaderInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
System.out.println("【ServerHeaderInterceptor】Received headers: " + headers);
// 可以在这里检查或修改请求头
return next.startCall(call, headers);
}
}
服务端拦截器 2
import io.grpc.*;
public class ServerLoggingInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
System.out.println("【ServerLoggingInterceptor】Server received call to method: " + call.getMethodDescriptor().getFullMethodName());
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(next.startCall(call, headers)) {
@Override
public void onMessage(ReqT message) {
System.out.println("【ServerLoggingInterceptor】Server received message: " + message);
super.onMessage(message);
}
@Override
public void onHalfClose() {
System.out.println("【ServerLoggingInterceptor】Server stream half-closed");
super.onHalfClose();
}
@Override
public void onComplete() {
System.out.println("【ServerLoggingInterceptor】Server stream completed");
super.onComplete();
}
@Override
public void onCancel() {
System.out.println("【ServerLoggingInterceptor】Server stream cancelled");
super.onCancel();
}
@Override
public void onReady() {
System.out.println("【ServerLoggingInterceptor】Server stream ready");
super.onReady();
}
};
}
}
客户端拦截器 1
import io.grpc.*;
public class ClientLoggingInterceptor implements ClientInterceptor {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
System.out.println("【ClientLoggingInterceptor】Client sending request to method: " + method.getFullMethodName());
super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {
@Override
public void onMessage(RespT message) {
System.out.println("【ClientLoggingInterceptor】Client received message: " + message);
super.onMessage(message);
}
@Override
public void onClose(Status status, Metadata trailers) {
System.out.println("【ClientLoggingInterceptor】Client call closed with status: " + status);
super.onClose(status, trailers);
}
}, headers);
}
@Override
public void sendMessage(ReqT message) {
System.out.println("【ClientLoggingInterceptor】Client sending message: " + message);
super.sendMessage(message);
}
};
}
}
3.6. 运行服务和客户端
启动服务器
- 编译并运行
HelloWorldServer
的main
方法。 - 服务器将在
localhost
的端口50051
上启动并开始监听。
启动客户端
- 编译并运行
HelloWorldClient
的main
方法。 客户端将连接到服务器并执行以下操作:
- 使用单一响应调用
SayHello
。 - 使用客户端流调用
SayHelloClientStream
。 - 使用服务器流调用
SayHelloServerStream
。 - 使用双向流调用
SayHelloBidirectionalStream
。
- 使用单一响应调用
使用测试化工具作为客户端
现在很多测试化工具以及支持调试 gRPC 服务了,例如 Apifox
,只需要将 .proto
接口描述文件上传到客户端,就可以自动生成客户端服务。
4. 代码解释
4.1. 同步与异步
GreeterBlockingStub
:用于同步阻塞调用,适合简单的请求-响应模式,不需要高并发。GreeterStubdd
:用于异步非阻塞调用,适合高并发和复杂流式通信。
选择使用哪种存根类,取决于你的应用需求和并发要求。在需要处理大量并发请求或复杂流式通信的场景下,GreeterStub
更加合适。而对于简单的请求-响应通信且不关心阻塞的场景,GreeterBlockingStub
则提供了更简单的编程模型。
4.1.1.GreeterGrpc.GreeterBlockingStub
同步调用:
GreeterBlockingStub
提供同步的阻塞调用。这意味着当你调用一个 RPC 方法时,调用线程会阻塞,直到服务器返回响应或发生错误。
使用场景:
- 适用于简单的
请求-响应模式
,客户端可以承受阻塞调用。 - 适用于需要按顺序处理请求,并且不需要高并发的场景。
- 适用于简单的
优点:
- 编程简单,易于理解和使用。
- 适合不需要高并发的简单应用。
缺点:
- 调用是阻塞的,可能会导致资源浪费,尤其是在需要处理大量并发请求时。
示例使用:
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051) .usePlaintext() .build(); GreeterGrpc.GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(channel); HelloRequest request = HelloRequest.newBuilder().setName("World").build(); HelloResponse response = blockingStub.sayHello(request); System.out.println("Greeting: " + response.getMessage());
4.1.2.GreeterGrpc.GreeterStub
异步调用:
GreeterStub
提供异步的非阻塞调用。调用一个 RPC 方法时,方法会立即返回,结果会在回调中处理。
使用场景:
- 适用于需要高并发和低延迟的场景。
- 适合复杂的流式通信模式(如
客户端流
、服务器流
和双向流
)。
优点:
- 非阻塞调用,提高了系统的并发能力和资源利用率。
- 更适合实时性要求高的应用。
缺点:
- 编程相对复杂,需要处理回调或使用异步框架来管理响应。
示例使用:
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051) .usePlaintext() .build(); GreeterGrpc.GreeterStub asyncStub = GreeterGrpc.newStub(channel); HelloRequest request = HelloRequest.newBuilder().setName("World").build(); asyncStub.sayHello(request, new StreamObserver<HelloResponse>() { @Override public void onNext(HelloResponse response) { System.out.println("Greeting: " + response.getMessage()); } @Override public void onError(Throwable t) { System.err.println("Error: " + t); } @Override public void onCompleted() { System.out.println("Completed"); } });
4.2. onCompleted
在 gRPC 中,当服务器端调用 StreamObserver#onCompleted()
时,客户端会收到通知。这是 gRPC 框架处理流式通信的一部分,用于指示流的正常结束。
服务器端调用
onCompleted()
:- 当服务器完成了对所有消息的处理并发送完所有需要的响应消息后,服务器端会调用
responseObserver.onCompleted()
。 - 这表示服务器端的响应流已经结束,没有更多的消息会发送到客户端。
- 当服务器完成了对所有消息的处理并发送完所有需要的响应消息后,服务器端会调用
客户端接收
onCompleted()
通知:- 客户端的
StreamObserver
实现会收到onCompleted()
调用的通知。 - 客户端可以在
onCompleted()
方法中执行一些收尾工作,比如释放资源或更新状态。
- 客户端的
4.2.1. 调用场景
1. 单一响应(Unary RPC)
- 客户端和服务器端:在单一响应模式中,
onCompleted()
不需要显式调用。请求和响应都是单个消息,gRPC 框架会自动处理流的结束。
2. 客户端流(Client Streaming RPC)
- 客户端:需要调用
onCompleted()
。客户端在发送完所有请求消息后,应调用onCompleted()
来通知服务器请求流已结束。服务器在接收到onCompleted()
后开始处理请求。 - 服务器端:不需要显式调用
onCompleted()
。服务器端在处理完所有请求后返回单个响应,流会自动结束。
3. 服务器流(Server Streaming RPC)
- 客户端:不需要调用
onCompleted()
。客户端发送单个请求后,等待服务器的响应流。 - 服务器端:需要调用
onCompleted()
。服务器在发送完所有响应消息后,调用onCompleted()
来通知客户端响应流已结束。
4. 双向流(Bidirectional Streaming RPC)
- 客户端:需要调用
onCompleted()
。客户端在发送完所有请求消息后,应调用onCompleted()
来通知服务器请求流已结束。 - 服务器端:需要调用
onCompleted()
。服务器在发送完所有响应消息后,调用onCompleted()
来通知客户端响应流已结束。
5. 编程规范
当然,在 gRPC 的单一响应模式中,虽然框架会自动处理流的结束,但明确调用 responseObserver.onCompleted()
是一个良好的编程实践。这么做有几个原因:
- 明确性和可读性:即使框架会自动处理调用,明确调用
onCompleted()
可以让代码更具可读性,清晰地表明流在此处结束。这有助于其他开发人员理解代码逻辑。 - 一致性:在所有流模式中显式调用
onCompleted()
可以保持代码风格的一致性。这有助于开发人员在处理不同类型的流时使用相同的模式和习惯。 - 未来的兼容性:虽然当前版本的 gRPC 可能在某些情况下自动处理
onCompleted()
,但显式调用确保代码在未来版本中的行为是一致的。 - 自定义行为:在某些情况下,你可能需要在调用
onCompleted()
之前执行一些特定的逻辑或清理操作。显式调用onCompleted()
让你有机会在结束流之前插入这些操作。
虽然在单一响应模式下显式调用 onCompleted()
并不是绝对必要的,但它仍然是一个推荐的做法,尤其是在团队开发中,有助于代码的维护和理解。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。