gRPC简单示例

gRPC概述

gRPC是一种跨语言的RPC框架,之所以它能跨语言,是因为它基于protobuf描述对象实体和方法,最后通过protobuf编译器生成指定语言的代码。
这样,就能通过一套protobuf声明生成多种语言的相同API,对于实现跨语言的RPC通信非常便利,同时也使用protobuf作为通信的序列化协议。

如下通过一个简单的示例展示如何在Java语言中基于gRPC实现一个C/S架构的通信模型。

使用步骤

安装protobuf编译器

下载并安装protobuf编译器,并将其bin路径添加到PATH变量中,如:D:\opt\protoc-3.13.0-win64\bin

添加protobuf-java依赖

在Maven项目中添加protobuf-java依赖:

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.13.0</version>
</dependency>

注:这里protobuf-java的版本必须与protobuf编译器的版本保持一致!

编写protobuf描述文件

编写protobuf描述文件(在Maven项目中通常将proto文件放在src/main/proto路径下)

// hello_world.proto
syntax = "proto3";

option java_multiple_files = true;                         // 每个message类型是否生成独立的文件
option java_outer_classname = "HelloWordProto";            // 当java_multiple_files=false时生成的多个message类的包装类名
option java_package = "org.chench.extra.java.grpc.proto";  // 生成的java文件所在包名

message HelloRequest {                                     // 通过message声明一个实体类
  string greeting = 1;                                     // 类对象属性
}

message HelloResponse {
  string reply = 1;
}

service HelloService {                                     // 通过service声明rpc类
  rpc SayHello (HelloRequest) returns (HelloResponse);     // rpc方法
}

编译protobuf描述文件

编译protobuf描述文件生成对应的Java类文件,有2种方式:

方式一:进入到protobuf描述文件路径,执行命令:protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/xxx.proto(注:$SRC_DIR$DST_DIR都必须是绝对路径,否则无法正确编译)
注:命令行编译的方式默认只会生成message声明的实体类,不会生成service声明的RPC类,解决办法参考:protoc不生成.proto中的service,只生成model相关类,求助

方式二:通过Maven插件编译:

<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.6.2</version>
        </extension>
    </extensions>
    <plugins>
        <!-- 使用如下插件编译proto文件 -->
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.6.1</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:3.13.0:exe:${os.detected.classifier}</protocArtifact>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.4.0:exe:${os.detected.classifier}</pluginArtifact>
                <!-- 指定proto文件位置 -->
                <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
                <!-- 指定proto文件编译后生成的java文件位置 -->
                <outputDirectory>${project.basedir}/src/main/java</outputDirectory>
                <!--设置是否在生成java文件之前清空outputDirectory的文件,默认值为true,设置为false时也会覆盖同名文件-->
                <clearOutputDirectory>false</clearOutputDirectory>
            </configuration>
            <executions>
                <execution>
                    <!--在执行mvn compile的时候会执行以下操作-->
                    <phase>compile</phase>
                    <goals>
                        <!--生成OuterClass类-->
                        <goal>compile</goal>
                        <!--生成Grpc类-->
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

在项目根目录下执行:mvn compile即可生成对应的java类。
protobuf编译生成Java类

简单rpc示例

服务端

// HelloWorldServer.java
public class HelloWorldServer {
    private static int port = 8181;
    private Server server;

    public static void main(String[] args) throws IOException, InterruptedException {
        HelloWorldServer helloWorldServer = new HelloWorldServer();
        helloWorldServer.start();
        helloWorldServer.blockUntilShutdown();
    }

    private void start() throws IOException {
        this.server = ServerBuilder.forPort(port)
                .addService(new HelloServiceImpl())
                .build()
                .start();
        logger.info(String.format("start server on port: %s", port));
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                logger.info("do stop...");
                try {
                    HelloWorldServer.this.stop();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                logger.info("stop done.");
            }
        });
    }

    private void stop() throws InterruptedException {
        if (this.server != null) {
            server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
        }
    }

    private void blockUntilShutdown() throws InterruptedException {
        if (this.server != null) {
            this.server.awaitTermination();
        }
    }

    // 服务端实现rpc接口
    class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
        @Override
        public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
            String greeting = request.getGreeting();
            logger.info(String.format("server receive greeting: %s", greeting));
            String responseMsg = new StringBuilder().append("Hello: ").append(greeting).toString();
            HelloResponse response = HelloResponse.newBuilder().setReply(responseMsg).build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }
    }
}

客户端

// HelloWorldClient.java
public class HelloWorldClient {
    private HelloServiceGrpc.HelloServiceBlockingStub blockingStub;

    public HelloWorldClient(Channel channel) {
        this.blockingStub = HelloServiceGrpc.newBlockingStub(channel);
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        String target = "localhost:8181";
        ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
                .usePlaintext()
                .build();
        HelloWorldClient helloWorldClient = new HelloWorldClient(channel);
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String line = null;
        while ((line = reader.readLine()) != null) {
            if ("quit".equals(line.trim())) {
                channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
                System.exit(0);
            } else {
                helloWorldClient.greeting(line);
            }
        }
    }

    // 调用远程RPC接口
    private void greeting(String greeting) {
        HelloRequest request = HelloRequest.newBuilder().setGreeting(greeting).build();
        HelloResponse response = this.blockingStub.sayHello(request);
        System.out.println(String.format("received response: %s", response.getReply()));
    }
}

【参考】
grpc-java
java使用protobuf-maven-plugin的插件编译proto文件
java语言中生成gprc代码的三种方式:gradle、protoc、镜像的方式

posted @ 2024-03-20 21:45  nuccch  阅读(45)  评论(0编辑  收藏  举报