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"
因此,客戶端能夠按預期呼叫伺服器,並且伺服器透過向客戶端問好來響應。