使用GRPC遠程服務調用

遠程過程調用(英語:Remote Procedure Call,縮寫爲 RPC)是一個計算機通訊協議。該協議容許運行於一臺計算機的程序調用另外一臺計算機的子程序,而程序員無需額外地爲這個交互做用編程。若是涉及的軟件採用面向對象編程,那麼遠程過程調用亦可稱做遠程調用或遠程方法調用。簡而言之,就是實現不一樣服務之間的相互調用的這麼一個協議,這個不一樣服務能夠是本地服務,也能夠是互聯網上的遠程服務。爲了容許不一樣的客戶端均能訪問服務器,許多標準化的 RPC 系統應運而生了。其中大部分採用接口描述語言(Interface Description Language,IDL 【對,Android中各個應用之間通信就是使用的IDL】),方便跨平臺的遠程過程調用。java

今天主要學習Google 開發的一個RPC框架—gRpc這是一個高性能的開源的rpc框架,具備如下特色(翻譯的不是很準確):git

  • Simple service definition 方便的定義服務
  • Works across languages and platforms 跨平臺、跨語言
  • Start quickly and scale 快速開發和大規模部署
  • Bi-directional streaming and integrated auth 雙向流設定和認證

下面咱們經過一個簡單的示例來看下gRpc的使用方法,先把代碼附上 GitHub代碼地址程序員

需求設定

這裏咱們假設須要請求服務計算基本的數字運算,客戶端發送兩個數字,服務端接收到數據數字後計算的到這兩個數字的和、差、積。需求很簡單,可是不要在客戶端計算啊,咱們的目的是演示,在客戶端計算就沒什麼意思了....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目錄下就會生成咱們須要的代碼,列表以下:
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);
  }
}

測試運行

測試運行步驟以下:

  • 啓動服務器 CalculationService 執行main方法
  • 啓動測試服務器 CalculationClient 執行main方法
  • 在測試服務器控制檯窗口輸入測試數據,觀察結果
相關文章
相關標籤/搜索