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方法開始,我們接受兩個引數,即namegreeting

  • 我們為與伺服器的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"

因此,客戶端能夠按預期呼叫伺服器,並且伺服器透過向客戶端問好來響應。

廣告
© . All rights reserved.