1

gRPC 是一个高性能、开源的远程过程调用(RPC)框架,由 Google 开发。它旨在提供跨语言的通信能力,适用于从移动设备到数据中心服务器的各种环境。

1. 核心概念

  1. Protocol Buffers(protobuf)

    • gRPC 使用 Protocol Buffers 作为其接口描述语言和数据序列化协议。开发者通过定义 .proto 文件来指定服务和消息格式。Protocol Buffers 提供了一种紧凑的二进制格式,支持快速序列化和反序列化。
  2. HTTP/2 协议

    • gRPC 基于 HTTP/2 进行通信。HTTP/2 提供了多路复用、头部压缩、流控制和双向流等特性,显著提高了传输效率和性能。
  3. 服务定义和代码生成

    • 开发者在 .proto 文件中定义服务和 RPC 方法。gRPC 编译器 protoc 根据这些定义生成客户端和服务器端的代码存根,简化了开发工作。
  4. 多种通信模式
    gRPC 支持四种服务方法类型,这使得 gRPC 可以适应多种应用场景:

    • 单一响应 RPC
    • 服务器流式 RPC
    • 客户端流式 RPC
    • 双向流式 RPC
  5. 安全性

    • gRPC 支持 TLS/SSL 加密,提供安全的通信通道。它还支持多种身份验证机制。

2. 实现原理

  1. 服务定义和编译
  • 定义服务:在 .proto 文件中定义服务接口和消息类型。

    syntax = "proto3";
    
    service Greeter {
        rpc SayHello (HelloRequest) returns (HelloResponse);
    }
    
    message HelloRequest {
        string name = 1;
    }
    
    message HelloResponse {
        string message = 1;
    }
  • 编译生成代码:使用 protoc 编译器生成客户端和服务器代码。这些代码包含了与服务交互所需的存根和骨架。
  1. 客户端和服务器的实现
  • 服务器端:实现生成的服务接口,定义具体的业务逻辑。启动 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());
  1. 传输层:基于 Netty 的实现
  • Netty 集成:gRPC 的 Java 实现使用 Netty 作为传输层框架。Netty 提供了异步事件驱动的 I/O 模型,支持高效的网络通信。
  • HTTP/2 支持:Netty 提供对 HTTP/2 的支持,使得 gRPC 可以利用 HTTP/2 的特性,如多路复用和头部压缩。
  1. 数据序列化和反序列化
  • gRPC 使用 Protocol Buffers 进行消息的序列化和反序列化。Protocol Buffers 提供了高效的二进制格式,减少了数据传输的开销。
  1. 拦截器和中间件
  • gRPC 支持拦截器机制,允许在请求和响应的生命周期中插入自定义逻辑。这类似于中间件,可以用于实现日志记录、认证、审计等功能。
  1. 负载均衡和服务发现
  • 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. 运行服务和客户端

启动服务器
  1. 编译并运行 HelloWorldServermain 方法。
  2. 服务器将在 localhost 的端口 50051 上启动并开始监听。
启动客户端
  1. 编译并运行 HelloWorldClientmain 方法。
  2. 客户端将连接到服务器并执行以下操作:

    • 使用单一响应调用 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 框架处理流式通信的一部分,用于指示流的正常结束。

  1. 服务器端调用 onCompleted():

    • 当服务器完成了对所有消息的处理并发送完所有需要的响应消息后,服务器端会调用 responseObserver.onCompleted()
    • 这表示服务器端的响应流已经结束,没有更多的消息会发送到客户端。
  2. 客户端接收 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() 是一个良好的编程实践。这么做有几个原因:

  1. 明确性和可读性:即使框架会自动处理调用,明确调用 onCompleted() 可以让代码更具可读性,清晰地表明流在此处结束。这有助于其他开发人员理解代码逻辑。
  2. 一致性:在所有流模式中显式调用 onCompleted() 可以保持代码风格的一致性。这有助于开发人员在处理不同类型的流时使用相同的模式和习惯。
  3. 未来的兼容性:虽然当前版本的 gRPC 可能在某些情况下自动处理 onCompleted(),但显式调用确保代码在未来版本中的行为是一致的。
  4. 自定义行为:在某些情况下,你可能需要在调用 onCompleted() 之前执行一些特定的逻辑或清理操作。显式调用 onCompleted() 让你有机会在结束流之前插入这些操作。

虽然在单一响应模式下显式调用 onCompleted() 并不是绝对必要的,但它仍然是一个推荐的做法,尤其是在团队开发中,有助于代码的维护和理解。


KerryWu
641 声望159 粉丝

保持饥饿


引用和评论

0 条评论