JAVA简单实现gRpc服务端和客户端

本文章同步到本人的博客站点 燕归来

远程过程调用(英语: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目录下就会生成我们需要的代码,列表如下:

image

服务端代码

服务端代码如下

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方法
  • 在测试服务器控制台窗口输入测试数据,观察结果

启动服务器

image

启动客户端并测试

image

停止服务端

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容