遠程過程調用(英語:Remote Procedure Call,縮寫爲 RPC)是一個計算機通訊協議。該協議容許運行於一臺計算機的程序調用另外一臺計算機的子程序,而程序員無需額外地爲這個交互做用編程。若是涉及的軟件採用面向對象編程,那麼遠程過程調用亦可稱做遠程調用或遠程方法調用。簡而言之,就是實現不一樣服務之間的相互調用的這麼一個協議,這個不一樣服務能夠是本地服務,也能夠是互聯網上的遠程服務。爲了容許不一樣的客戶端均能訪問服務器,許多標準化的 RPC 系統應運而生了。其中大部分採用接口描述語言(Interface Description Language,IDL 【對,Android中各個應用之間通信就是使用的IDL】),方便跨平臺的遠程過程調用。java
今天主要學習Google 開發的一個RPC框架—gRpc這是一個高性能的開源的rpc框架,具備如下特色(翻譯的不是很準確):git
下面咱們經過一個簡單的示例來看下gRpc的使用方法,先把代碼附上 GitHub代碼地址程序員
這裏咱們假設須要請求服務計算基本的數字運算,客戶端發送兩個數字,服務端接收到數據數字後計算的到這兩個數字的和、差、積。需求很簡單,可是不要在客戶端計算啊,咱們的目的是演示,在客戶端計算就沒什麼意思了....github
這裏咱們先說一下,邊寫的環境信息編程
注意:build.gradle的配置內容不要隨意更改服務器
咱們須要邊寫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
目錄下就會生成咱們須要的代碼,列表以下:
ide
服務端代碼以下性能
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); } }
測試運行步驟以下: