跳至主要內容

GRPC-基础

holic-x...大约 6 分钟架构RPC

GRPC-基础

学习核心

todo:https://apifox.com/apiskills/protocol-buffers-protoc-setup/

学习资料

GRPC-helloworld

1.项目构建

构建步骤

JDK版本:Java 8

① 创建一个springboot项目(或者maven模板项目即可),此处用一个项目grpc-demo进行模拟(serverclient),生成基础代码(生成基础代码的方式有两种)

  • 基于maven插件
  • 基于proto官方提供的插件(protocprotoc-gen-grpc-java

② 生成基础代码(消息对象和gRPC接口),随后分别构建服务端和客户端

③ 模拟测试

⚽ 基础代码生成

(1)maven 插件方式

proto 文件 配置

​ 此处构建helloworld.proto文件(可以在src/main下创建proto文件夹存放相关的proto文件)

//Protocal Buffers的版本有v2和v3之分,语法有较多变化,且相互不兼容
//这里使用的v3版本的
syntax = "proto3";

//编译后生成的消息类HelloRequest和HelloReply是否分别放在单独的class文件中
option java_multiple_files = true;
//生成代码的包路径
option java_package = "com.noob.grpc.demo";

//最外层的类名称
option java_outer_classname = "HelloWorldProto";

//包命名空间
package helloworld;

// 服务接口
service Greeter {
  // 一个简单的rpc方法
  rpc SayHello (HelloRequest) returns (HelloReply) {}

}

// 请求消息
message HelloRequest {
  string name = 1;
}

// 响应消息
message HelloReply {
  string message = 1;
}

maven 插件配置(grpc相关插件)

​ 在pom.xml位置装配依赖

<dependencies>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-netty-shaded</artifactId>
        <version>1.16.1</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>1.16.1</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>1.16.1</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>
                <protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.16.1:exe:${os.detected.classifier}</pluginArtifact>
                <!-- protoSourceRoot 指定proto文件存放位置(根据这个位置的proto文件生成)如果不指定则默认是与src/main/java同级的proto目录下的文件 -->
                <!-- <protoSourceRoot>src/main/resources/proto/</protoSourceRoot>-->
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

​ maven 配置完成,刷新依赖,随后在maven组件窗口点击compile,随后可以看到插件会根据自定义的proto文件生成相关的class(在target的指定文件夹下),生成的内容包括供服务端实现的服务接口和供客户端进行服务调用Stub代理类、请求消息和响应消息相关的类和接口

image-20241218163652668

​ 此处需注意,要用protobuf插件辅助生成相关的java文件,compile生成消息对象,compile-custom生成接口,将生成的java文件放置在源码中直接应用即可

image-20241218182334363

​ 也可以直接执行mvn clean install来生成

image-20241218185205265

(2) proto命令行方式

​ 借助官方提供的编译器,将protobuf文件转成相应的Java代码,相关插件说明如下:

​ 相关软件下载完成,在window环境下可以配置环境变量,便于日常使用。例如此处将相关组件放置在

​ 还可参考另一种方式,借助官网提供的proto.exe命令行(D:\software\dev\grpc\protoc-3.0.0-win32\bin\protoc.exe)来执行操作(通过官方的工具生成类protoc-3.0.0-win32.zipopen in new window生成基础类),例如在指定位置存放好定义的proto文件,随后执行指令

① 执行命令生成消息对象

# 输出java语言基础类(.表示在当前路径生成)
protoc.exe helloworld.proto --java_out=.
    
# 输出c++语言基础类
protoc.exe helloworld.proto --cpp_out=.

image-20241218164800278

② 执行命令生成gRPC接口

# 输出java语言基础类(.表示在当前路径生成)
# protoc.exe --plugin=protoc-gen-grpc-java=protoc-gen-grpc-java.exe --grpc-java_out=./out helloworld.proto  // 系统找不到指定文件,可能是环境变量配置没生效
protoc.exe --plugin=protoc-gen-grpc-java=D:/dev/tools/protoc-gen-grpc-java/protoc-gen-grpc-java.exe --grpc-java_out=. helloworld.proto  

# 参考命令
protoc.exe --plugin=protoc-gen-grpc-java=protoc-gen-grpc-java.exe --grpc-java_out=java --proto_path=proto proto/xxxx.proto

​ 上述命令指向完成,可以在指定的路径生成GreeterGrpc.java

⚽ 构建服务端和客户端

​ 通过上述步骤生成基础的java类,GreeterGrpc.java(grpc接口)和相关消息实体定义,其中GreeterGrpc封装基本的GRPC功能,后续的客户端和服务端都从这个类引申出来。将上述构建的基础类都放到源文件包中,然后分别构建服务端和客户端

服务端

/**
 * HelloWorldServer
 **/
public class HelloWorldServer {

    private static final Logger log = Logger.getLogger(HelloWorldServer.class.getName());


    //扩展gRPC自动生成的服务接口,实现业务功能
    static class GreeterImpl extends GreeterGrpc.GreeterImplBase {

        @Override
        public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {

            //构建响应消息,从请求消息中获取姓名,在前面拼接上"Hello "
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();

            //在流关闭或抛出异常前可以调用多次
            responseObserver.onNext(reply);

            //关闭流
            responseObserver.onCompleted();

        }
    }


    public static void main(String[] args) throws IOException, InterruptedException {

        //服务要监听的端口
        int port = 50051;

        //创建服务对象,监听端口,注册服务并启动
        Server server = ServerBuilder.
                forPort(port)  //监听50051端口
                .addService(new GreeterImpl()) //注册服务
                .build()  //创建Server对象
                .start(); //启动

        log.info("Server started,listening on " + port);

        server.awaitTermination();

    }

}

客户端

/**
 * HelloWorldClient
 **/
public class HelloWorldClient {

    private static final Logger log = Logger.getLogger(HelloWorldClient.class.getName());

    public static void main(String[] args) {


        String host = "localhost";

        int port = 50051;

        //1.创建ManagedChannel,绑定服务端ip地址和端口
        ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext()
                .build();

        //2.获得同步调用的stub对象
        GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);

//        //获得异步调用的stub对象
//        GreeterGrpc.GreeterFutureStub futureStub = GreeterGrpc.newFutureStub(channel);

        Scanner scanner = new Scanner(System.in);
        while (true) {
            //从控制台读取用户输入
            String name = scanner.nextLine().trim();
            //构建请求消息
            HelloRequest helloRequest = HelloRequest.newBuilder().setName(name).build();
            //通过stub代理对象进行服务调用,获取服务端响应
            HelloReply helloReply = stub.sayHello(helloRequest);
            final String message = helloReply.getMessage();
            log.warning("Greeting: " + message);
        }
    }
}

⚽ 项目启动测试

​ 先启动server后启动client,随后通过在client的输入控制台键入参数,请求访问server就会获取到响应的结果并输出到控制台

image-20241218195532544

可能遇见的错误问题

​ JDK 版本(在JDK17版本下提示下述错误),考虑是JDK版本升级对模块化的改造导致(降低到JDK8版本可正常执行(无下述提示))

image-20241218200131628

​ 消除下述提示(参考解决方案open in new window),可以在启动的时候加上JVM Option--add-opens java.base/jdk.internal.misc=ALL-UNNAMED --illegal-access=warn(编辑启动配置,键入快捷键ALT+V快速进行编辑),这个命令是用来配置Java 9及以上版本的运行时行为的

java.lang.IllegalAccessException: class io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0$6 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @18bf3d14
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392)
	at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674)
	at java.base/java.lang.reflect.Method.invoke(Method.java:561)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0$6.run(PlatformDependent0.java:334)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:325)

​ 而对于的处理则考虑是不同JDK版本对反射模块的处理,虽然不影响运行,但启动的时候会提示这个信息(硬核一点就是降低JDK版本为9以下,例如1.8版本)

java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled
	at io.grpc.netty.shaded.io.netty.util.internal.ReflectionUtil.trySetAccessible(ReflectionUtil.java:31)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0$4.run(PlatformDependent0.java:224)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:218)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:212)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent.<clinit>(PlatformDependent.java:80)
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3