gRPC - Java版Hello World应用
现在让我们创建一个基本的类似“Hello World”的应用程序,它将使用gRPC和Java。
.proto 文件
首先,让我们在common_proto_files中定义"greeting.proto"文件:
syntax = "proto3";
option java_package = "com.tp.greeting";
service Greeter {
rpc greet (ClientInput) returns (ServerOutput) {}
}
message ClientInput {
string greeting = 1;
string name = 2;
}
message ServerOutput {
string message = 1;
}
现在让我们仔细看看这段代码块,并了解每一行的作用:
syntax = "proto3";
这里的“syntax”表示我们正在使用的Protobuf版本。因此,我们使用的是最新版本3,因此该模式可以使用所有对版本3有效的语法。
package tutorial;
这里的包用于冲突解决,例如,如果我们有多个具有相同名称的类/成员。
option java_package = "com.tp.greeting";
此参数特定于Java,即从“.proto”文件自动生成代码的包。
service Greeter {
rpc greet(ClientInput) returns (ServerOutput) {}
}
此块表示服务“Greeter”的名称和可调用的函数名“greet”。“greet”函数接收类型为“ClientInput”的输入,并返回类型为“ServerOutput”的输出。现在让我们看看这些类型。
message ClientInput {
string greeting = 1;
string name = 2;
}
在上面的代码块中,我们定义了ClientInput,它包含两个属性"greeting"和"name",它们都是字符串。客户端应该将类型为"ClientInput"的对象发送到服务器。
message ServerOutput {
string message = 1;
}
在上面的代码块中,我们定义了,给定一个"ClientInput",服务器将返回"ServerOutput",它将包含单个属性"message"。服务器应该将类型为"ServerOutput"的对象发送到客户端。
请注意,我们已经完成了Maven设置,可以自动生成我们的类文件以及我们的RPC代码。因此,现在我们可以简单地编译我们的项目:
mvn clean install
这应该会自动生成我们使用gRPC所需的源代码。源代码将放置在:
Protobuf class code: target/generated-sources/protobuf/java/com.tp.greeting Protobuf gRPC code: target/generated-sources/protobuf/grpc-java/com.tp.greeting
设置gRPC服务器
现在我们已经定义了包含函数定义的proto文件,让我们设置一个可以服务于调用这些函数的服务器。
让我们编写我们的服务器代码来服务上述函数,并将其保存在com.tp.grpc.GreetServer.java中:
示例
package com.tp.grpc;
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;
import com.tp.greeting.GreeterGrpc;
import com.tp.greeting.Greeting.ClientInput;
import com.tp.greeting.Greeting.ServerOutput;
public class GreetServer {
private static final Logger logger = Logger.getLogger(GreetServer.class.getName());
private Server server;
private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port).addService(new GreeterImpl()).build().start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("Shutting down gRPC server");
try {
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
});
}
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void greet(ClientInput req, StreamObserver<ServerOutput> responseObserver) {
logger.info("Got request from client: " + req);
ServerOutput reply = ServerOutput.newBuilder().setMessage(
"Server says " + "\"" + req.getGreeting() + " " + req.getName() + "\""
).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
final GreetServer greetServer = new GreetServer();
greetServer.start();
greetServer.server.awaitTermination();
}
}
上面的代码在指定的端口启动一个gRPC服务器,并服务于我们在proto文件中编写的函数和服务。让我们一起学习上面的代码:
从main方法开始,我们在指定的端口创建一个gRPC服务器。
但在启动服务器之前,我们将要运行的服务分配给服务器,即在我们的例子中是Greeter服务。
为此,我们需要将服务实例传递给服务器,因此我们继续创建一个服务实例,即在我们的例子中是GreeterImpl。
服务实例需要提供“.proto”文件中存在的method/function的实现,即在我们的例子中是greet方法。
该方法期望一个类型为“.proto”文件中定义的对象,即在我们的例子中是ClientInput。
该方法对上述输入进行计算,然后应该返回“.proto”文件中提到的输出,即在我们的例子中是ServerOutput。
最后,我们还有一个shutdown钩子,以确保在完成代码执行时服务器能够干净地关闭。
设置gRPC客户端
现在我们已经编写了服务器的代码,让我们设置一个可以调用这些函数的客户端。
让我们编写我们的客户端代码来调用上述函数,并将其保存在com.tp.grpc.GreetClient.java中:
示例
package com.tp.grpc;
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;
import com.tp.greeting.GreeterGrpc;
import com.tp.greeting.Greeting.ServerOutput;
import com.tp.greeting.Greeting.ClientInput;
public class GreetClient {
private static final Logger logger = Logger.getLogger(GreetClient.class.getName());
private final GreeterGrpc.GreeterBlockingStub blockingStub;
public GreetClient(Channel channel) {
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void makeGreeting(String greeting, String username) {
logger.info("Sending greeting to server: " + greeting + " for name: " + username);
ClientInput request = ClientInput.newBuilder().setName(username).setGreeting(greeting).build();
logger.info("Sending to server: " + request);
ServerOutput response;
try {
response = blockingStub.greet(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Got following from the server: " + response.getMessage());
}
public static void main(String[] args) throws Exception {
String greeting = args[0];
String username = args[1];
String serverAddress = "localhost:50051";
ManagedChannel channel = ManagedChannelBuilder.forTarget(serverAddress)
.usePlaintext()
.build();
try {
GreetClient client = new GreetClient(channel);
client.makeGreeting(greeting, username);
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
}
上面的代码在指定的端口启动一个gRPC服务器,并服务于我们在proto文件中编写的函数和服务。让我们一起学习上面的代码:
从main方法开始,我们接受两个参数,即name和greeting。
我们为与服务器的gRPC通信设置一个Channel。
接下来,我们使用创建的channel创建一个阻塞式stub。在这里,我们拥有计划调用的函数的服务“Greeter”。stub只不过是一个隐藏了远程调用复杂性的包装器。
然后,我们简单地创建“.proto”文件中定义的预期输入,即在我们的例子中是ClientInput。
我们最终进行调用并等待服务器的结果。
最后,我们关闭channel以避免任何资源泄漏。
所以,这就是我们的客户端代码。
客户端服务器调用
现在,我们已经定义了我们的proto文件,编写了我们的服务器和客户端代码,让我们继续执行此代码并查看实际情况。
要运行代码,请启动两个shell。通过执行以下命令在第一个shell中启动服务器:
java -cp .\target\grpc-point-1.0.jar com.tp.grpc.GreetServer
我们将看到以下输出:
输出
Jul 03, 2021 1:04:23 PM com.tp.grpc.GreetServer start INFO: Server started, listening on 50051
上述输出意味着服务器已启动。
现在,让我们启动客户端。
java -cp .\target\grpc-point-1.0.jar com.tp.grpc.GreetClient Hello Jane
我们将看到以下输出:
输出
Jul 03, 2021 1:05:59 PM com.tp.grpc.GreetClient greet INFO: Sending greeting to server: Hello for name: Jane Jul 03, 2021 1:05:59 PM com.tp.grpc.GreetClient greet INFO: Sending to server: greeting: "Hello" name: "Jane" Jul 03, 2021 1:06:00 PM com.tp.grpc.GreetClient greet INFO: Got following from the server: Server says "Hello Jane"
现在,如果我们打开服务器日志,我们将看到以下内容:
Jul 03, 2021 1:04:23 PM com.tp.grpc.GreetServer start INFO: Server started, listening on 50051 Jul 03, 2021 1:06:00 PM com.tp.grpc.GreetServer$GreeterImpl greet INFO: Got request from client: greeting: "Hello" name: "Jane"
因此,客户端能够按预期调用服务器,并且服务器通过向客户端问好来响应。