
👆關注微信公眾號,獲取更多編程內容
原文鏈接 https://www.zhoutao123.com/page/book/architect/category/yb8cpz
遠程過程調用(英語:Remote Procedure Call,縮寫為 RPC)是一個計算機通信協議。該協議允許運行於一台計算機的程序調用另一台計算機的子程序,而程序員無需額外地為這個交互作用編程。如果涉及的軟件采用面向對象編程,那么遠程過程調用亦可稱作遠程調用或遠程方法調用。簡而言之,就是實現不同服務之間的相互調用的這么一個協議,這個不同服務可以是本地服務,也可以是互聯網上的遠程服務。為了允許不同的客戶端均能訪問服務器,許多標准化的 RPC 系統應運而生了。其中大部分采用接口描述語言(Interface Description Language,IDL 【對,Android中各個應用之間通訊就是使用的IDL】),方便跨平台的遠程過程調用。
今天主要學習Google 開發的一個RPC框架—gRpc這是一個高性能的開源的rpc框架,具有以下特點(翻譯的不是很准確):
- Simple service definition 方便的定義服務
- Works across languages and platforms 跨平台、跨語言
- Start quickly and scale 快速開發和大規模部署
- Bi-directional streaming and integrated auth 雙向流設定和認證
下面我們通過一個簡單的示例來看下gRpc的使用方法,先把代碼附上 GitHub代碼地址
需求設定
這里我們假設需要請求服務計算基本的數字運算,客戶端發送兩個數字,服務端接收到數據數字后計算的到這兩個數字的和、差、積。需求很簡單,但是不要在客戶端計算啊,我們的目的是演示,在客戶端計算就沒什么意思了....
服務編寫
這里我們先說一下,邊寫的環境信息
- IDEA
- JDK8
- Gralde
注意:build.gradle的配置內容不要隨意更改
Proto文件邊寫
我們需要邊寫proto文件,文件的格式可以參考Protobuf語言指南——.proto文件語法詳解里面講的很詳細,代碼如下:
//聲明版本
syntax = 'proto3';
//設定一些選項信息
option java_multiple_files = true;
option java_package = "com.tao.example.grpc.basic";
option java_outer_classname = "BasicGprc";
option objc_class_prefix = "HLW";
package basic;
//定義服務
service Grpc {
//定義Rpc,名稱為 calculation
//請求參數類型為 GrpcRequest
//響應參數類型為 GrpcResponse
rpc calculation(GrpcRequest) returns(GrpcResponse) {}
}
//在消息定義中,每個字段都有唯一的一個標識符。
//這些標識符是用來在消息的二進制格式中識別各個字段的,一旦開始使用就不能夠再改變。
//定義請求參數
message GrpcRequest {
string num1 = 1;
string num2 = 2;
}
//定義響應參數
message GrpcResponse {
string sum = 1;
string sub = 2;
string product = 3;
}
完成代碼的邊寫后,在Gradle使用任務去編譯這個proto文件 ,任務名稱為 generateProto
,執行之后在·build/generated/source/proto/main
目錄下就會生成我們需要的代碼,列表如下:
服務端代碼
服務端代碼如下
package com.tao.example;
import com.tao.example.grpc.basic.GrpcGrpc;
import com.tao.example.grpc.basic.GrpcRequest;
import com.tao.example.grpc.basic.GrpcResponse;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
public class CalculationService {
protected static int SERVER_PORT = 8888;
private static final Logger logger = Logger.getLogger(CalculationService.class.getName());
private Server server;
/**
* 啟動服務
*
* @param port
* @throws IOException
*/
private void start(int port) throws IOException {
server = ServerBuilder.forPort(port).addService(new BasicCalImpl()).build().start();
logger.log(Level.INFO, "服務已經啟動,監聽端口:" + port);
Runtime.getRuntime()
.addShutdownHook(
new Thread(
() -> {
logger.log(Level.WARNING, "監聽到JVM停止,正在關閉GRPC服務....");
CalculationService.this.stop();
logger.log(Level.WARNING, "服務已經停止...");
}));
}
/** 關閉服務 */
public void stop() {
Optional.of(server).map(s -> s.shutdown()).orElse(null);
}
/**
* 循環運行服務,封鎖停止
*
* @throws InterruptedException
*/
public void blockUnitShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/**
* 程序的主運行窗口
*
* @param args
* @throws IOException
* @throws InterruptedException
*/
public static void main(String[] args) throws IOException, InterruptedException {
CalculationService service = new CalculationService();
service.start(SERVER_PORT);
service.blockUnitShutdown();
}
/** 實現的服務類 */
static class BasicCalImpl extends GrpcGrpc.GrpcImplBase {
@Override
public void calculation(GrpcRequest request, StreamObserver<GrpcResponse> responseObserver) {
// 獲取數據信息
int num1 = Integer.parseInt(request.getNum1());
int num2 = Integer.parseInt(request.getNum2());
// 計算數據
GrpcResponse response =
GrpcResponse.newBuilder()
.setSum(String.valueOf(num1 + num2))
.setSub(String.valueOf(num1 - num2))
.setProduct(String.valueOf(num1 * num2))
.build();
// 返回數據,完成此次請求
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
}
客戶端代碼
客戶端代碼和服務端類似,可對比學習。
package com.tao.example;
import com.tao.example.grpc.basic.GrpcGrpc;
import com.tao.example.grpc.basic.GrpcRequest;
import com.tao.example.grpc.basic.GrpcResponse;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import static com.tao.example.CalculationService.SERVER_PORT;
public class CalculationClient {
private static final Logger logger = Logger.getLogger(CalculationClient.class.getName());
private ManagedChannel managedChannel;
private GrpcGrpc.GrpcBlockingStub blockingStub;
public CalculationClient(String host, int port) {
this(ManagedChannelBuilder.forAddress(host, port).usePlaintext(true));
}
public void sendMessage(String num1, String num2) {
logger.log(Level.INFO, "嘗試發送: num1 = " + num1 + ",num2 = " + num2);
GrpcRequest request = GrpcRequest.newBuilder().setNum1(num1).setNum2(num2).build();
GrpcResponse response = null;
try {
response = blockingStub.calculation(request);
System.out.println("兩數的和 = " + response.getSum());
System.out.println("兩數的差 = " + response.getSub());
System.out.println("兩數的積 = " + response.getProduct());
} catch (StatusRuntimeException ex) {
logger.log(Level.WARNING, "發送消息出現異常", ex);
}
}
/**
* 關閉客戶端
*
* @throws InterruptedException
*/
public void shutdown() throws InterruptedException {
managedChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
CalculationClient(ManagedChannelBuilder<?> channelBuilder) {
managedChannel = channelBuilder.build();
blockingStub = GrpcGrpc.newBlockingStub(managedChannel);
}
public static void main(String[] args) throws IOException, InterruptedException {
String host = "127.0.0.1";
CalculationClient client = new CalculationClient(host, SERVER_PORT);
Scanner scanner = new Scanner(System.in);
Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
System.out.print("請輸入Num1:");
String num1Str = scanner.next();
if (!pattern.matcher(num1Str).matches()) {
logger.log(Level.WARNING, "num1不是一個整數,程序無法運行");
}
System.out.print("請輸入Num2:");
String num2Str = scanner.next();
if (!pattern.matcher(num2Str).matches()) {
logger.log(Level.WARNING, "num2不是一個整數,程序無法運行");
}
client.sendMessage(num1Str, num2Str);
}
}
測試運行
測試運行步驟如下:
- 啟動服務器 CalculationService 執行main方法
- 啟動測試服務器 CalculationClient 執行main方法
- 在測試服務器控制台窗口輸入測試數據,觀察結果