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";

这里的"语法"代表我们正在使用的 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 类代码:target/generated-sources/protobuf/java/com.tp.greeting
Protobuf gRPC 代码: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"文件中存在的方法/函数的实现,即我们例子中的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 方法开始,我们接受两个参数,即 namegreeting

  • 我们设置了一个用于与服务器进行 gRPC 通信的通道。

  • 接下来,我们使用创建的通道创建一个阻塞存根。这里我们拥有服务"Greeter",我们计划调用其函数。stub 只不过是一个包装器,它向调用者隐藏了远程调用的复杂性。

  • 然后,我们只需创建在".proto"文件中定义的预期输入,即我们例子中的 ClientInput

  • 我们最终进行调用并等待服务器的结果。

  • 最后,我们关闭通道以避免任何资源泄漏。

所以,这就是我们的客户端代码。

客户端服务器调用

现在,我们已经定义了我们的 proto 文件,编写了我们的服务器和客户端代码,让我们继续并执行此代码并查看实际操作。

要运行代码,请启动两个 shell。通过执行以下命令在第一个 shell 上启动服务器 −

java -cp .	arget\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 .	arget\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"

因此,客户端能够按预期调用服务器,并且服务器也会向客户端发出问候。