这篇文章将介绍如何使用grpc-java开发一个简单的单向RPC服务。

引入依赖

新建一个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
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.43.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.43.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.43.1</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>

然后添加protocmaven插件:

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
<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.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.19.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.43.1:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

接下来我们就可以通过编写.proto文件来定义rpc接口和数据类型,默认情况下.proto文件需放置在src/main/protosrc/test/proto目录中(如没有则相应新建文件夹)。

编写.proto文件

下面编写一个helloworld.proto文件定义一个简单的rpc服务,.proto文件内容如下:

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
syntax = "proto3";

// 为每个Java类生成单独的.java文件
option java_multiple_files = true;
// 生成的Java包名
option java_package = "org.sunchaser.sparrow.javaee.grpc.core.helloworld";
// 生成的Java类名
option java_outer_classname = "HelloWorldProto";

// 指定Java包名,被option java_package选项覆盖
package helloworld;

// 定义rpc服务
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// 定义message
message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

以上就定义了一个简单rpc,客户端使用存根发送请求到服务端并等待响应返回,就像调用本地方法一样调用rpc服务。定义了一个SayHello rpc服务,入参HelloRequest,返参HelloReply

生成grpcprotobuf相关类

1
2
mvn protobuf:compile
mvn protobuf:compile-custom

protoc

使用IDEAmaven插件快速执行以上命令,在target目录下可看到生成的相关类如下图所示。

target

编写grpc服务端

编写HelloWorldServer服务端如下:

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
package org.sunchaser.sparrow.javaee.grpc.core.helloworld;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

/**
* grpc 服务端
* @author sunchaser admin@lilu.org.cn
* @since 2022/1/13
*/
public class HelloWorldServer {

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

private Server server;

private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
.start();

log.info("GRPC Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
try {
HelloWorldServer.this.stop();
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
System.err.println("*** server shut down");
}));
}

private void stop() throws InterruptedException {
if (server != null) {
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
}
}

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

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

static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
HelloReply helloReply = HelloReply.newBuilder()
.setMessage("Hello " + request.getName())
.build();
responseObserver.onNext(helloReply);
responseObserver.onCompleted();
}
}
}

运行main方法后控制台输出:

1
2
一月 17, 2022 2:25:39 下午 HelloWorldServer start
信息: GRPC Server started, listening on 50051

编写grpc客户端

编写HelloWorldClient客户端如下:

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
package org.sunchaser.sparrow.javaee.grpc.core.helloworld;

import io.grpc.Channel;
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;

/**
* grpc客户端
* @author sunchaser admin@lilu.org.cn
* @since 2022/1/14
*/
public class HelloWorldClient {
private static final Logger log = Logger.getLogger(HelloWorldClient.class.getName());

private final GreeterGrpc.GreeterBlockingStub blockingStub;

public HelloWorldClient(Channel channel) {
// 初始化远程服务存根stub
blockingStub = GreeterGrpc.newBlockingStub(channel);
}

public void greet(String name) {
log.info("Will try to greet " + name + " ...");
// 构造服务调用入参对象
HelloRequest request = HelloRequest.newBuilder()
.setName(name)
.build();
HelloReply response;
try {
// 调用rpc远程服务
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
log.log(Level.WARNING, "gRPC failed: {0}", e.getStatus());
return;
}
// 输出返回值
log.info("Greeting: " + response.getMessage());
}

public static void main(String[] args) throws InterruptedException {
String user = "grpc";
String target = "localhost:50051";
if (args.length > 0) {
if ("--help".equals(args[0])) {
System.err.println("Usage: [name [target]]");
System.err.println("");
System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
System.err.println(" target The server to connect to. Defaults to " + target);
System.exit(1);
}
user = args[0];
}
if (args.length > 1) {
target = args[1];
}
// 远程连接管理器,管理连接的生命周期。初始化远程连接
ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
.usePlaintext()
.build();
try {
// 创建客户端
HelloWorldClient client = new HelloWorldClient(channel);
// 远程rpc服务调用
client.greet(user);
} finally {
// 关闭连接
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
}

可通过main方法的启动参数传入usertarget,默认user="grpc";target="localhost:50051"表示,调用刚才我们编写好的服务端,运行main方法后控制台输出如下:

1
2
3
4
一月 17, 2022 2:31:23 下午 HelloWorldClient greet
信息: Will try to greet grpc ...
一月 17, 2022 2:31:24 下午 HelloWorldClient greet
信息: Greeting: Hello grpc

可以看到已经完成了一次远程rpc服务调用。