念念不忘
必会回响

Java gRPC 小例子

最近准备着手学习下gRPC,就先以一个小demo作为开始吧。

1. 创建Maven项目

刚开始学习就没必要着急与SpringBoot结合了,先试试最传统的Java项目。其pom.xml内容如下:

<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>rpcdemo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>rpcdemo</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <protobuf.version>3.21.12</protobuf.version>
    <grpc.version>1.53.0</grpc.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-netty-shaded</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>
    <dependency>
      <groupId>com.google.protobuf</groupId>
      <artifactId>protobuf-java</artifactId>
      <version>${protobuf.version}</version>
    </dependency>
    <!-- 如果是Java8以上的,需要添加这个依赖,看issue,似乎grpc也没得办法 -->
    <!-- https://github.com/grpc/grpc-java/issues/8086 -->
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
      <version>1.3.2</version>
    </dependency>
  </dependencies>

  <build>
    <extensions>
      <extension>
        <groupId>kr.motd.maven</groupId>
        <artifactId>os-maven-plugin</artifactId>
        <version>1.5.0.Final</version>
      </extension>
    </extensions>
    <plugins>
      <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>0.5.1</version>
        <configuration>
          <pluginId>grpc-java</pluginId>
          <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
          <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
          <!-- proto文件放置的目录 -->
          <protoSourceRoot>src/main/resources/proto</protoSourceRoot>
          <!-- 生成文件的目录, 可以让其在源码目录生成 -->
          <!-- <outputDirectory>${project.basedir}/src/main/java</outputDirectory>-->
          <!-- 生成文件前是否把目标目录清空,这个最好设置为false,以免误删项目文件 -->
          <!-- <clearOutputDirectory>false</clearOutputDirectory>-->
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>11</source>
          <target>11</target>
        </configuration>
      </plugin>

    </plugins>
  </build>
</project>

2. 创建.proto文件

根据pom.xml中指定的路径,在src/main/resources/proto中创建一个名为helloworld.proto文件,其内容如下:

//在非空非注释的第一行指定当前文件使用的是proto3的语法,默认proto2
syntax = "proto3";
//package与java_package有些不同,java_package是定义编译生成的java文件所在的目录,而package是对应的java类的命名空间
package example;
option java_package = "cn.net.dev.grpc";
//要生成Java类的名称
option java_outer_classname = "HelloWorldServiceProto";
//编译后会生成多个Message类,并没有被包含在HelloWorldServiceProto.java文件中,反之,生成单一HelloWorldServiceProto.java文件
option java_multiple_files = true;

//定义服务端接口类
service Greeter {
  // //服务端接口方法 Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// //请求参数 基于序号的协议字段映射,所以字段可以乱序,可缺段 The request message containing the user's name.
message HelloRequest {
  string name = 1;
}
// //响应参数 The response message containing the greetings
message HelloReply {
  string message = 1;
}

在IDE中,点击MAVEN侧边栏->plugins->protobuf->protobuf:compile,protobuf:compile-custom,或者直接在控制台中输入mvn compile,mvn compile-custom

protobuf:compile // 编译消息对象

protobuf:compile-custom // 依赖消息对象,生成接口服务

具体可参考 Maven Protocol Buffers Plugin – protobuf:compile (xolstice.org)

编译完成后,会在target/generated-sources/protobuf/java/cn/net/dev/grpc文件夹中生成helloworld.proto对应消息的Java代码,在target/generated-sources/protobuf/grpc-java/cn/net/dev/grpc文件夹中生成helloworld.proto对应服务的Java代码。如果想把这些代码的生成路径放在源码(src目录)中,则打开outputDirectory注释就行,注意clearOutputDirectory要设置为false,避免清空其他代码。我看了很多例子,很少由把代码生成到源码目录的,所以我也打算保持这个习惯。

3. 创建Server端

先创建一个gRPC Server

package cn.net.dev.grpc.server;

import cn.net.dev.grpc.GreeterGrpc;
import cn.net.dev.grpc.HelloReply;
import cn.net.dev.grpc.HelloRequest;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.logging.Logger;

public class HelloWorldServer {
    private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
    private int port = 50051;
    private Server server;

    public HelloWorldServer() {
    }

    private void start() throws IOException {
        this.server = ServerBuilder.forPort(this.port).addService(new GreeterImpl()).build().start();
        logger.info("Server started, listening on " + this.port);
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                System.err.println("*** shutting down gRPC server since JVM is shutting down");
                HelloWorldServer.this.stop();
                System.err.println("*** server shut down");
            }
        });
    }

    private void stop() {
        if (this.server != null) {
            this.server.shutdown();
        }

    }

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

    }

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

    static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
        GreeterImpl() {
        }

        public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
        }
    }
}

4. 创建Client端

package cn.net.dev.grpc.client;

import cn.net.dev.grpc.GreeterGrpc;
import cn.net.dev.grpc.HelloReply;
import cn.net.dev.grpc.HelloRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HelloWorldClient {
    private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
    //客户端与服务器的通信channel
    private final ManagedChannel channel;
    //客户端与服务器的通信channel
    private final GreeterGrpc.GreeterBlockingStub blockingStub;

    public HelloWorldClient(String host, int port) {
         //指定grpc服务器地址和端口初始化通信channel
        this.channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
        //根据通信channel初始化客户端存根节点
        this.blockingStub = GreeterGrpc.newBlockingStub(this.channel);
    }

    public void shutdown() throws InterruptedException {
        this.channel.shutdown().awaitTermination(5L, TimeUnit.SECONDS);
    }

    public void greet(String name) {
        logger.info("Will try to greet " + name + " ...");
        HelloRequest request = HelloRequest.newBuilder().setName(name).build();

        HelloReply response;
        try {
            response = this.blockingStub.sayHello(request);
        } catch (StatusRuntimeException var5) {
            logger.log(Level.WARNING, "RPC failed: {0}", var5.getStatus());
            return;
        }

        logger.info("Greeting: " + response.getMessage());
    }

    public static void main(String[] args) throws Exception {
        HelloWorldClient client = new HelloWorldClient("localhost", 50051);

        try {
            String user = "world";
            if (args.length > 0) {
                user = args[0];
            }

            client.greet(user);
        } finally {
            client.shutdown();
        }

    }
}

由于protobuf的代码生产到target目录了,所以刚开始我一直找不到包,但可以点击过去,在我重启idea以后这个问题解决了,有这类问题的可以试试这个办法。

然后先执行服务端,再执行客户端,客户端会收到如下信息:

Connected to the target VM, address: '127.0.0.1:61011', transport: 'socket'
3月 19, 2023 11:16:59 上午 cn.net.dev.grpc.client.HelloWorldClient greet
信息: Will try to greet world ...
3月 19, 2023 11:17:00 上午 cn.net.dev.grpc.client.HelloWorldClient greet
信息: Greeting: Hello world
Disconnected from the target VM, address: '127.0.0.1:61011', transport: 'socket'

Process finished with exit code 0

项目结构

D:.
│  pom.xml
├─src
│  ├─main
│  │  ├─java
│  │  │  ├─cn
│  │  │  │  └─net
│  │  │  │      └─dev
│  │  │  │          └─grpc
│  │  │  │              ├─client
│  │  │  │              │      HelloWorldClient.java
│  │  │  │              │
│  │  │  │              └─server
│  │  │  │                      HelloWorldServer.java
│  │  │  │
│  │  │  └─org
│  │  │      └─example
│  │  │              App.java
│  │  │
│  │  └─resources
│  │      └─proto
│  │              helloworld.proto
│  │
│  └─test
│      └─java
│          └─org
│              └─example
│                      AppTest.java
│
└─target
    │
    ├─generated-sources
    │  ├─annotations
    │  └─protobuf
    │      ├─grpc-java
    │      │  └─cn
    │      │      └─net
    │      │          └─dev
    │      │              └─grpc
    │      │                      GreeterGrpc.java
    │      │
    │      └─java
    │          └─cn
    │              └─net
    │                  └─dev
    │                      └─grpc
    │                              HelloReply.java
    │                              HelloReplyOrBuilder.java
    │                              HelloRequest.java
    │                              HelloRequestOrBuilder.java
    │                              HelloWorldServiceProto.java
    │
    │
    └─test-classes
        └─org
            └─example
                    AppTest.class

赞(7) 打赏
未经允许不得转载:堆上小栈 » Java gRPC 小例子

评论 抢沙发

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续提供更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫

微信扫一扫

登录

找回密码

注册