gRPC
什么是gRPC?
- gRPC是由Google公司开源的高性能RPC框架。
- gRPC支持多语言
gRPC原生使用C、Java、Go进行了三种实现,而C语言实现的版本进行封装后又支持了C++、C#、Node...
- gRPC支持多平台
支持的平台包括:Linux、Android、IOS、MacOS、Windows
- gRPC的消息协议使用Google自家开源的Protocol Buffers协议机制(proto3)
- gRPC的传输使用HTTP/2标准,支持双向流和连接多路复用
使用方法
- 使用Protocol Buffers (proto3) 的 IDL 接口定义语言定义接口服务,编写在文本文件(以
.proto
为后缀名)中。 - 使用 protobuf 编译器生成服务器和客户端使用的 stub 代码
- 编写补充服务器和客户端逻辑代码
HTTP/2
gRPC的传输是基于HTTP/2标准协议实现的,前面提到的支持双向流和多路复用,实际就是HTTP/2的特性。而且gRPC有四种接口类型,也是依赖HTTP/2协议建立起来的,所以我们有必要先来了解一下HTTP/2协议。
HTTP/1.x
HTTP/1.x 是一个文本协议,可读性很好,但是效率不高
HTTP/1.x 的解析很复杂,
HTTP/1.x 交互模式是一个连接每次只能一问一答,也就是请求之后必须要等到响应才能进行下一次请求
HTTP/1.x 没有服务器推送功能,只能采取轮询的方式。
HTTP/2
HTTP/2 协议是一个二进制协议,这也就意味着它的可读性为0,但是效率高
这里有三个比较重要的概念,分别是数据流、消息和帧
- 数据流 Stream: 已建立的连接内的双向字节流,可以承载一条或多条消息。
- 消息 Message: 与逻辑请求或响应消息对应的完整一系列帧
- 帧 Frame: HTTP/2 通信的最小单位,每个帧都包含帧头,至少也会标识出当前帧所属的数据流
这里就不展开说了,可以自己找资料看一下
gRPC接口类型
gRPC有四种接口类型:
- Unary RPC (一元RPC) 一个请求一个响应
- Server Streaming RPC (服务端流式RPC) 一个请求多个响应
- Client Streaming RPC (客户端流式RPC) 多个请求一个响应
- Bidrectional Streaming RPC (双向流式RPC) 多个请求多个响应
这些都是数据流 Stream,我们所说的接口类型是指进行一次gRPC调用的数据通讯流程(或数据流Stream的生命周期)
Protocol Buffers
Protocol Buffers 是一种与语言无关,平台无关的可扩展机制,用于序列化结构化数据。使用 Protocol Buffers 可以一次定义结构化的数据,然后可以使用特殊生成的源代码轻松地在各种数据流中使用各种编程语言编写和读取结构化数据
文档结构
Protocol Buffers 文档的版本声明
protobuf
syntax = "proto3";
Protocol Buffers 可以声明 package,来防止命名冲突。
使用的时候通过 包名.xxx 使用
protobuf
package foo.bar;
中可以导入其他文件消息等
protobuf
import "myproject/other_protos.proto";
注释
arduino
// 单行注释
/* 多行注释 */
与Java相关的语法
protobuf
// protobuf 生成的代码是一个源文件还是多个源文件
option java_multiple_files = false;
// 指定 protobuf 生成的类,放置在哪个包中
option java_package = "com.basepackage";
// 外层类的名字
option java_outer_classname = "xxx.java";
数据类型
- 基本数据类型
Proto Type | Java/Kotlin Type[1] |
---|---|
double | double |
float | float |
int32 | int |
int64 | long |
uint32 | int[2] |
uint64 | long[2] |
sint32 | int |
sint64 | long |
fixed32 | int[2] |
fixed64 | long[2] |
sfixed32 | int |
sfixed64 | long |
bool | boolean |
string | String |
bytes | ByteString |
- 枚举
在 proto3 中,枚举定义中定义的第一个值必须 具有 零,枚举值不能重复,枚举值不能重复,除非使用 option allow_alias = true;
来开启别名
枚举器常量必须在 32 位整数的范围内。
protobuf
enum Corpus {
option allow_alias = true;
CORPUS_UNSPECIFIED = 0;
CORPUS_UNIVERSAL = 1;
CORPUS_WEB = 2;
CORPUS_IMAGES = 3;
CORPUS_LOCAL = 4;
CORPUS_NEWS = 5;
CORPUS_PRODUCTS = 6;
CORPUS_VIDEO = 7;
}
- 消息
Protocol Buffers 使用 message 定义消息数据,在 Protocol Buffers 中使用的数据都是通过 message 消息数据封装基本数据类型或其他消息数据
编号不能使用 [19000-19999],这个范围是Protocol Buffers预留的
消息可以嵌套,也可以在消息内部声明消息
protobuf
message SearchRequest {
string query = 1;// 等号后面是字段的编号
int32 page_number = 2;
int32 results_per_page = 3;
repeated message = 4;
}
- singular : 修饰 message 中的字段,这个字段的值只能是0个或一个(默认关键字)
- repeated : 修饰 message 中的字段,这个字段的值是多个,等价于 java 中的 List 集合
消息中可以指定保留字段:
保留变量不被使用
这些字段的编号、标识符被预留下来,不能直接使用
protobuf
message Foo {
reserved 2,15,9 to 11;
reserved "foo","bar";
}
- 映射
如果要创建关联映射作为数据定义的一部分, Protocol Buffers 提供了一个方便的快捷语法:
ini
map<key_type, value_type> map_field = N;
- oneof
其中一个,选择其中一种字段作为类型
protobuf
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
定义服务
可以定义多个方法,定义的方法都是以 rpc 开头,前面有提到四种接口类型,其中一元rpc就是不在请求消息和响应消息加stream,其他的就是加上对应的stream
protobuf
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse){}
}
service SearchService {
rpc 方法名([stream] 请求消息类型) returns ([stream] 响应消息类型){}
}
安装编译器
直接去官网下载
Releases · protocolbuffers/protobuf
我的版本是 29.0
下载解压到你的目录即可
然后把bin目录添加到path环境变量
通过protoc --version
验证是否成功
项目开发
创建api模块
编写.proto
文件
执行 protoc --java_out=/xxx/xxx /xxx/xx.proto
命令第一个参数是生成代码的文件目录,后面的是proto文件位置
或者使用 maven 插件编译,一下是需要导入的依赖和插件
xml
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.71.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.71.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.71.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.71.0:exe:${os.detected.classifier}</pluginArtifact>
生成代码的目录-->
<outputDirectory>${basedir}/src/main/java</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
分别执行这两个命令就可以自动生成出来代码
HelloProto:对应数据
HelloProtoGrpc:对应服务
创建service模块
导入上面创建的api模块的依赖
xml
<dependency>
<groupId>com.huang.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
创建一个类实现我们定义的服务接口
java
// 集成服务实现 xxxxImplBase
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase{
/**
* 实现我们定义的服务接口
* @param request 客户端发过来的请求参数
* @param responseObserver 返回值
*/
@Override
public void sayHello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {
// 接受客户端的请求参数并打印
System.out.println("receive request: " + request.getName());
// 封装响应参数
HelloProto.HelloResponse response = HelloProto.HelloResponse.newBuilder()
.setResult("hello " + request.getName())//调用对应的set方法设置参数
.build();
// 发送响应
responseObserver.onNext(response);
// 通知客户端发送完成
responseObserver.onCompleted();
}
}
创建服务端
java
public class GrpcService {
public static void main(String[] args) {
ServerBuilder<?> serverBuilder = ServerBuilder.forPort(9000);
// 添加服务
serverBuilder.addService(new HelloServiceImpl());
// 创建服务对象
Server server = serverBuilder.build();
try {
// 启动服务
server.start();
// 阻塞主线程
server.awaitTermination();
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端
创建客户端模块
导入上面创建的api模块
xml
<dependency>
<groupId>com.huang.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
编写客户端程序
我这里为了看起来简洁没有关闭资源,你可以自己在finally的代码块中关闭资源channel.shutdown()
java
public class GrpcClient {
public static void main(String[] args) {
// 创建通信管道
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build();
// 获得代理对象 stub
HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel);
// 准备参数
HelloProto.HelloRequest request = HelloProto.HelloRequest.newBuilder()
.setName("huang")
.build();
// 发送请求
HelloProto.HelloResponse helloResponse = stub.sayHello(request);
// 答应获取的结果
System.out.println(helloResponse.getResult());
}
}
这里是给出的一元RPC的案例,上面我们说了有四种接口类型大家可以自己去了解一下其他接口类型的实现方法,代码大体上是差不多的