GRPC-基础
GRPC-基础
学习核心
todo:https://apifox.com/apiskills/protocol-buffers-protoc-setup/
学习资料
GRPC-helloworld
1.项目构建
构建步骤
JDK版本:Java 8
① 创建一个springboot
项目(或者maven模板项目即可),此处用一个项目grpc-demo
进行模拟(server
、client
),生成基础代码(生成基础代码的方式有两种)
- 基于
maven
插件 - 基于
proto
官方提供的插件(protoc
、protoc-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代理类、请求消息和响应消息相关的类和接口
此处需注意,要用protobuf
插件辅助生成相关的java
文件,compile
生成消息对象,compile-custom
生成接口,将生成的java
文件放置在源码中直接应用即可
也可以直接执行mvn clean install
来生成
(2) proto
命令行方式
借助官方提供的编译器,将protobuf
文件转成相应的Java代码,相关插件说明如下:
- 【protoc软件】:用于处理proto文件的工具软件,对proto文件生成消息对象和序列化及反序列化的Java实体类
- 【protoc-gen-grpc-java插件】:用于处理rpc定义的插件,生成针对rpc定义的Java接口
- 【wrapper.proto】:项目中如果用到了包装类型就需要下载这个文件,如果没使用到,则不需要
相关软件下载完成,在window环境下可以配置环境变量,便于日常使用。例如此处将相关组件放置在
还可参考另一种方式,借助官网提供的proto.exe
命令行(D:\software\dev\grpc\protoc-3.0.0-win32\bin\protoc.exe
)来执行操作(通过官方的工具生成类protoc-3.0.0-win32.zip生成基础类),例如在指定位置存放好定义的proto
文件,随后执行指令
① 执行命令生成消息对象
# 输出java语言基础类(.表示在当前路径生成)
protoc.exe helloworld.proto --java_out=.
# 输出c++语言基础类
protoc.exe helloworld.proto --cpp_out=.
② 执行命令生成
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
就会获取到响应的结果并输出到控制台
可能遇见的错误问题
JDK 版本(在JDK17版本下提示下述错误),考虑是JDK版本升级对模块化的改造导致(降低到JDK8版本可正常执行(无下述提示))
消除下述提示(参考解决方案),可以在启动的时候加上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)