gRPC入门java示例

gRPC 入门 java 示例

前言

入职新公司一周后,一直在干看代码,感觉理解起来进度很慢,现在终于有任务给我了

是需要写一个基于 gRPC 的接口

开始学习起来

学东西首先要看的就是官网

https://grpc.io/

这是 java入门教程: https://grpc.io/docs/languages/java/quickstart/

Intro

看官网就够够了: https://grpc.io/docs/what-is-grpc/introduction/

In gRPC, a client application can directly call a method on a server application on a different machine as if it were a local object, making it easier for you to create distributed applications and services. As in many RPC systems, gRPC is based around the idea of defining a service, specifying the methods that can be called remotely with their parameters and return types. On the server side, the server implements this interface and runs a gRPC server to handle client calls. On the client side, the client has a stub (referred to as just a client in some languages) that provides the same methods as the server.

Concept Diagram

gRPC clients and servers can run and talk to each other in a variety of environments - from servers inside Google to your own desktop - and can be written in any of gRPC’s supported languages. So, for example, you can easily create a gRPC server in Java with clients in Go, Python, or Ruby. In addition, the latest Google APIs will have gRPC versions of their interfaces, letting you easily build Google functionality into your applications.

RPC 就不赘述了, 说一下 gRPC

gRPC 就是 RPC 的一种实现,简单来说就是

  1. 定义了一份通信标准
  2. server 端去实现这个通信标准并且启动 gRPC 服务器去处理 client 端的请求.
  3. client 端有一份 stub(存根),提供和 server 端一样的方法
  4. server 和 client 端可以是不同机器不同环境不同语言下

Protocol Buffers

众所周知,RPC 协议一个重点就是商榷传输的规则,gRPC 采用的是 Protocol Buffers 的方式

关于 Protocol Buffers 的具体情况有机会再写一篇

简而言之就是写了一份 .proto文件,根据这个文件利用插件生成 server 端和 client 端的对应语言的代码

生成代码需要下载安装 protoc

下载地址见 github: https://github.com/protocolbuffers/protobuf/releases, 需要将/src添加到环境变量

protocol buffer java 生成教程: https://developers.google.com/protocol-buffers/docs/reference/java-generated

java 示例一: 官网(太监)

我直接跟着官网走的,这里有一个https://grpc.io/docs/languages/java/basics/

编写protoc 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
option objc_class_prefix = "RTG";

package routeguide;

service RouteGuide {

rpc GetFeature(Point) returns (Feature) {}

rpc ListFeatures(Rectangle) returns (stream Feature) {}

rpc RecordRoute(stream Point) returns (RouteSummary) {}
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}

message Point {
int32 latitude = 1;
int32 longitude = 2;
}

message Rectangle {
Point lo = 1;

Point hi = 2;
}

message Feature {
string name = 1;

Point location = 2;
}

message FeatureDatabase {
repeated Feature feature = 1;
}

message RouteNote {
Point location = 1;

string message = 2;
}

message RouteSummary {
int32 point_count = 1;

int32 feature_count = 2;

int32 distance = 3;

int32 elapsed_time = 4;
}

编译生成 java 类

生成 proto 对应的代码

刚开始我直接 protoc

1
protoc --java_out=src/main/java/ src/main/proto/route_guide.proto # 使用下一步的代码

仅仅生成了proto 文件对应的代码

生成 grpc 代码

网上google了之后发现是需要使用插件才可以生成 grpc 代码

maven 仓库: https://repo1.maven.org/maven2/io/grpc/protoc-gen-grpc-java/1.38.1/

链接是 1.38 .1版本的

发现都是.exe 结尾的,我是 mac 怎么办,后来查询到只需要给予执行权限就行了

找到对应版本下载后,执行

1
chmod u+x protoc-gen-grpc-java-1.38.1-osx-x86_64.exe

然后执行

1
2
3
4
5
protoc \
> --java_out=src/main/java/ \
> --plugin=protoc-gen-grpc-java=/Users/yiqing/Downloads/protoc-gen-grpc-java-1.38.1-osx-x86_64.exe \
> --grpc-java_out=src/main/java/ \
> src/main/proto/route_guide.proto

即可发现新增了关键的 RouteGuideGrpc.java 文件

好的,终于可以继续看教程了,下一步就是编写服务端和客户端的代码了


好的,鄙人不才,官网的示例感觉根本运行不起来,教程很过时

所以重新做了一个简单的示例

java 示例二: 简洁版

实现简单的客户端上传两个int,返回两个 int 之和

创建 maven 项目

话不多说

修改 pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?xml version="1.0" encoding="UTF-8"?>
<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>grpcjava</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
<grpc.version>1.34.1</grpc.version>
<protobuf.version>3.12.0</protobuf.version>
<protoc.version>3.12.0</protoc.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
<version>${grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf.version}</version>
</dependency>
</dependencies>


<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<protoSourceRoot>src/main/resources/proto</protoSourceRoot>
</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>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

这个方法是采用 maven 编译生成 grpc 代码的

创建proto 文件

注意 pom 中的 protoSourceRoot 目录,在该目录下新建 proto 文件, add.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
syntax = "proto3";

option java_package = "zone.yiqing.grpc";
option java_outer_classname = "AddServiceProto";
option java_multiple_files = true;

service AddService{
rpc add(AddRequest) returns (AddReply){}
}

message AddRequest{
int32 a = 1;
int32 b = 2;
}

message AddReply{
int32 res = 1;
}

其中标注了语法为proto3

根据 proto 生成类

在 maven 中直接点击 maven install

但是看到项目中没有生成任何文件

去 target 中,查看 generated-sources 中,protobuf 文件夹

其中重要的是我们生成的 AddServiceImplBase 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static abstract class AddServiceImplBase implements io.grpc.BindableService {

/**
*/
public void add(zone.yiqing.grpc.AddRequest request,
io.grpc.stub.StreamObserver<zone.yiqing.grpc.AddReply> responseObserver) {
asyncUnimplementedUnaryCall(getAddMethod(), responseObserver);
}

@java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
.addMethod(
getAddMethod(),
asyncUnaryCall(
new MethodHandlers<
zone.yiqing.grpc.AddRequest,
zone.yiqing.grpc.AddReply>(
this, METHODID_ADD)))
.build();
}
}

可以看到 add 方法已经添加

传入参数为 AddRequest

返回参数为 void 的原因: gRPC 的入参和返回值都在参数里,即io.grpc.stub.StreamObserver<zone.yiqing.grpc.AddReply> responseObserver.这里通过一个对象去监听了返回值

真正实现 addService 的话,需要继承这个AddServiceImplBase并且重写方法

编写 Server 端

  1. 在项目中新建一个类 AddService,写一个 main 方法,并继承AddServiceGrpc.AddServiceImplBase

    可见尽管在 target 中,也是可以继承的

  2. 重写 add 方法, idea 快捷键control + o

  3. 删掉里面的东西,添加自己写的方法,返回两者之和

    注意 AddReply 的构造方法

  4. 在 main 方法中启动 server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @author yiqing.zhang
* @date 2021-06-24.
*/
public class AddServer extends AddServiceGrpc.AddServiceImplBase {

public static void main(String[] args) throws IOException {
ServerBuilder
.forPort(7777)
.addService(new AddServer())
.build()
.start();
System.out.println("server on 7777");
}

@Override
public void add(AddRequest request, StreamObserver<AddReply> responseObserver) {
int add = myAdd(request.getA(), request.getB());
responseObserver.onNext(AddReply.newBuilder().setRes(add).build());
responseObserver.onCompleted();
}

public int myAdd(int a, int b) {
return a + b;
}
}

编写 Client 端

  1. 在项目中新建 AddClient 类
  2. 添加一个构造类,其中创建一个 channel,并且创建一个 stub
    1. stub是用来请求的,请求的时候 client 是通过 将参数传给 stub,stub 传给服务端的
  3. 在 main 方法中创建 channel 和发起请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @author yiqing.zhang
* @date 2021-06-24.
*/
public class AddClient {

AddServiceBlockingStub stub;
ManagedChannel channel;

public static void main(String[] args) {
AddClient client = new AddClient();

int a = 1;
int b = 1;

AddReply addReply = client.stub.add(AddRequest.newBuilder().setA(a).setB(b).build());
System.out.println(addReply.getRes());
}

public AddClient() {
channel = ManagedChannelBuilder
.forAddress("127.0.0.1", 7777) //要去连接的 server 地址和端口
.usePlaintext() // 文本的类型
.build();
stub = AddServiceGrpc.newBlockingStub(channel);
}
}

启动 server

运行 server,发现 server 立即退出,说明应该是新开了一个线程去创建 server,当主线程结束后也退出了.

修改一下代码添加 while(true){}

可以看到端口已经在运行了:

1
2
3
yiqings-laptop:~ yiqing$ lsof -i:7777
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 36278 yiqing 64u IPv6 0x37ddc7b92240c489 0t0 TCP *:cbt (LISTEN)

启动 client

可以看到控制台打印出了结果 2

done!