第一个gRPC程序

本文将记录我初次学习gRPC的一些成果和历程,gRPC是谷歌开发的基于protobuf的一款高性能,开源的通用RPC框架,支持多种语言。可以在任何环境中运行。 它可以有效地连接数据中心内和跨数据中心的服务,并提供可插拔的支持,以实现负载平衡,跟踪,健康检查和身份验证。 它还适用于分布式计算的最后一英里,用于将设备,移动应用程序和浏览器连接到后端服务。

1、工具:IDEA,Gradle

2、下载安装Gradle,参见另外一篇博文:https://blog.csdn.net/ccf199201261/article/details/100058377

3、在IDEA中新建一个Gradle项目,新建项目时使用本地安装的Gradle,我这边的目录结构如下

4、从https://github.com/grpc/grpc-java/blob/master/README.md上将gRPC开发有关的gradle依赖和protobuf生成java代码的gradle插件拷到项目的build.gradle中。

apply plugin: 'java'
apply plugin: 'com.google.protobuf'

group 'com.dxy'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    //grpc依赖
    implementation 'io.grpc:grpc-netty-shaded:1.23.0'
    implementation 'io.grpc:grpc-protobuf:1.23.0'
    implementation 'io.grpc:grpc-stub:1.23.0'
}


//protobuf生成java代码的gradle插件
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
    }
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.9.0"
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.23.0'
        }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
        }
    }
}

此时用gradle clean build命令构建一下项目,便可以生成很多gradle的task,比如像一会会用到的generateProto,用来将proto文件生成java代码,如下图所示,

5、准备工作做得差不多了,接下来去gRPC官网看一看,从官网可知gRPC的接口支持4种形式,如下图

1):最简单的一种也是最常见最好理解的一种形式,既发送一个请求返回一下响应,就像平时的一个方法调用

2):服务器端流式RPC,客户端向服务器发送请求并获取流以读取消息序列。 客户端从返回的流中读取,直到没有更多消息。

3):客户端流式RPC,客户端再次使用提供的流写入一系列消息并将其发送到服务器。 一旦客户端写完消息,它就等待服务器           全部读取它们并返回它的响应。

4):双向流式RPC,双方使用读写流发送一系列消息。 这两个流独立运行,因此客户端和服务器可以按照自己喜欢的顺序进行           读写:例如,服务器可以在写入响应之前等待接收所有客户端消息,或者它可以交替地读取消息然后写入消息, 或者其他            一些读写组合。 保留每个流中的消息顺序。

6、终于进入正题,编写一个proto文件,并放到src/main/proto目录下,这是上面提到的插件决定的默认存放位置,但是可配置的,见https://grpc.io/docs/reference/java/generated-code/

proto文件内容如下:

syntax = "proto3";

package com.dxy.proto;
option java_package = "com.dxy.proto";
option java_outer_classname = "Human";
//生成多个java文件
option java_multiple_files=true;

message MyRequest{
  string code = 1;
}

message MyResponse{
  string req_code = 1;
  string msg = 2;
}

//grpc接口服务,下面4个方法对应gRPC的4中请求方式
service HumanService{
  //类似调用常用方法,一个请求一个响应
  rpc GetRequestByCode(MyRequest) returns (MyResponse){}
  //服务器端流式RPC,一个请求返回一个流式响应
  rpc GetStreamInfo(MyRequest) returns (stream MyResponse){}
  //客户端流式RPC,一个流式请求,返回一个普通响应
  rpc GetInfoByStreamRequest(stream MyRequest) returns (MyResponse){}
  //双向流式RPC,一个流式请求,返回一个流式响应
  rpc GetStreamInfoByStreamRequest(stream MyRequest) returns (stream MyResponse){}
}

proto文件编写完成后,使用 gradle generateProto 命令自动生成java代码,代码会默认生成到build/generated/source/proto目录下。如果需要让java代码自动生成到项目指定的目录下,比如src/main/java目录,则需要在上面的插件配置处加两个配置项,具体请详细阅读protobuf关于gradle的插件在github上的代码说明,地址https://github.com/google/protobuf-gradle-plugin ,主要阅读下面截图的内容

这里提到两个参数一个是generatedFilesBaseDir,另一个是outputSubDir,第一个参数是生成代码的外层目录位置,默认是build\generated\source\proto,然后里面还有main/java或是main/grpc,前者是存放message相关类的,后者是存放service相关rpc服务类的。那现在我们需要存放的外层目录是src/main/java,这下就很明显了,只需要将build\generated\source\proto改成src即可,也就是将第一个参数generatedFilesBaseDir设置成src。那第二个参数又是什么意思呢,outputSubDir指的是子目录,它其实具体指的是service也就是rpc服务代码存放的目录的子目录,上面已经说过了service的默认存放目录的子目录是main/grpc,那么outputSubDir的默认值便是grpc,这样就很清楚了,只要将outputSubDir设置成java就好了,这样不管是message还是service的类都会被生成到src/main/java目录下的对应包下了。因此将上面的build.gradle文件的插件相关代码改成如下内容即可:

//protobuf生成java代码的gradle插件
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
    }
}

protobuf {
    generatedFilesBaseDir = "src"
    protoc {
        artifact = "com.google.protobuf:protoc:3.9.0"
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.23.0'
        }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {
                 outputSubDir = 'java'
            }
        }
    }
}

7、编写接口实现类,实现生成的4个方法

package com.dxy.grpc;

import com.dxy.proto.HumanServiceGrpc;
import com.dxy.proto.MyRequest;
import com.dxy.proto.MyResponse;
import io.grpc.stub.StreamObserver;

public class HumanServiceImpl extends HumanServiceGrpc.HumanServiceImplBase {

    @Override
    public void getRequestByCode(MyRequest request, StreamObserver<MyResponse> responseObserver) {

        System.out.println(request.getCode());
        responseObserver.onNext(MyResponse.newBuilder().setReqCode(request.getCode()).setMsg("Response Message!").build());
        responseObserver.onCompleted();
    }

    @Override
    public void getStreamInfo(MyRequest request, StreamObserver<MyResponse> responseObserver) {
        System.out.println("getStreamInfo:"+request.getCode());
        responseObserver.onNext(MyResponse.newBuilder().setReqCode(request.getCode()).setMsg("Stream Response Message1!").build());
        responseObserver.onNext(MyResponse.newBuilder().setReqCode(request.getCode()).setMsg("Stream Response Message2!").build());
        responseObserver.onNext(MyResponse.newBuilder().setReqCode(request.getCode()).setMsg("Stream Response Message3!").build());
        responseObserver.onCompleted();
    }

    /**
     * 由于该接口的返回不是流式,如果在StreamObserver<MyRequest>()的onNext()方法去写返回的话会报警告:
     * 警告: Cancelling the stream with status Status{code=INTERNAL, description=Too many responses, cause=null}
     * 因此这种情况只能在客户端的请求流结束onCompleted()方法中去进行响应返回
     * @param responseObserver
     * @return
     */
    @Override
    public StreamObserver<MyRequest> getInfoByStreamRequest(StreamObserver<MyResponse> responseObserver) {

        return new StreamObserver<MyRequest>() {
            @Override
            public void onNext(MyRequest value) {
                System.out.println("Received a request:"+value.getCode());
                //这样会被警告,并出现错误的结果
//                responseObserver.onNext(MyResponse.newBuilder().setReqCode("Multiple Request Code,").setMsg("Multiple Request Code!").build());
            }

            @Override
            public void onError(Throwable t) {
                System.out.println(t.getMessage());
            }

            @Override
            public void onCompleted() {
                System.out.println("Request Completed!");
                responseObserver.onNext(MyResponse.newBuilder().setReqCode("Multiple Request Code,").setMsg("Multiple Request Code!").build());
                responseObserver.onCompleted();
            }
        };
    }

    /**
     * 在这种双向流的情况下,可以实现一边接收请求一边响应结果,并在请求结束时完成响应
     * @param responseObserver
     * @return
     */
    @Override
    public StreamObserver<MyRequest> getStreamInfoByStreamRequest(StreamObserver<MyResponse> responseObserver) {
        return new StreamObserver<MyRequest>(){

            @Override
            public void onNext(MyRequest value) {
                System.out.println("接收到客户端信息:"+value.getCode());
                //接收到客户端请求的同时发送一条消息到服务端
                responseObserver.onNext(MyResponse.newBuilder().setReqCode(value.getCode()).setMsg("Response Message").build());
            }

            @Override
            public void onError(Throwable t) {
                System.out.println(t.getMessage());

            }

            @Override
            public void onCompleted() {
                System.out.println("Request Completed!");
                responseObserver.onCompleted();
            }
        };
    }

}

8、编写服务类

package com.dxy.grpc;

import io.grpc.Server;
import io.grpc.ServerBuilder;

public class HumanServer {

    private Server server;

    private void start() throws Exception{
        server = ServerBuilder.forPort(8899).addService(new HumanServiceImpl()).build();
        server.start();
        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            System.out.println("关闭jvm");
            HumanServer.this.stop();
        }));

    }

    private void stop(){
        server.shutdown();
    }

    private void awaitTermination() throws Exception{
        server.awaitTermination();
    }
    public static void  main(String[] args) throws Exception{
        final HumanServer humanServer = new HumanServer();
        humanServer.start();
        humanServer.awaitTermination();
    }
}

9、编写客户端代码

package com.dxy.grpc;

import com.dxy.proto.HumanServiceGrpc;
import com.dxy.proto.MyRequest;
import com.dxy.proto.MyResponse;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;

import java.util.concurrent.TimeUnit;

public class HumanClient {

    public static void main(String[] args) throws Exception{
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost",8899).usePlaintext().build();
        //阻塞式请求
//        HumanServiceGrpc.HumanServiceBlockingStub blockingStub = HumanServiceGrpc.newBlockingStub(channel);
//        MyResponse response = blockingStub.getRequestByCode(MyRequest.newBuilder().setCode("ABC").build());
//        System.out.println(response.getReqCode() + "," + response.getMsg());

        /*************************************************************************/
        //非阻塞式
        HumanServiceGrpc.HumanServiceStub stub = HumanServiceGrpc.newStub(channel);
//        stub.getStreamInfo(MyRequest.newBuilder().setCode("BCD").build(),new StreamObserver<MyResponse>(){
//
//            @Override
//            public void onNext(MyResponse value) {
//                System.out.println(value.getReqCode()+","+value.getMsg());
//            }
//
//            @Override
//            public void onError(Throwable t) {
//                System.out.println(t.getMessage());
//
//            }
//
//            @Override
//            public void onCompleted() {
//
//                System.out.println("Completed!");
//
//            }
//        });


        /**********************************************************************/

//        StreamObserver<MyRequest> streamObserverRequest = stub.getInfoByStreamRequest(new StreamObserver<MyResponse>() {
//            @Override
//            public void onNext(MyResponse value) {
//                //获取服务端返回结果
//                System.out.println(value.getReqCode()+","+value.getMsg());
//            }
//
//            @Override
//            public void onError(Throwable t) {
//                System.out.println(t.getMessage());
//            }
//
//            @Override
//            public void onCompleted() {
//                //获取服务端返回结果结束
//                System.out.println("Response Completed!");
//            }
//        });
//        for(int i=0;i<10;i++){
//            //用异步流的方式向服务端发送10次数据
//            streamObserverRequest.onNext(MyRequest.newBuilder().setCode(""+i).build());
//        }
//        //数据发送完成
//        streamObserverRequest.onCompleted();

   /*********************************************************************************/

        StreamObserver<MyRequest> streamObserverRequest2 =  stub.getStreamInfoByStreamRequest(new StreamObserver<MyResponse>(){

            @Override
            public void onNext(MyResponse value) {
                System.out.println(value.getReqCode()+","+value.getMsg());
            }

            @Override
            public void onError(Throwable t) {
                System.out.println(t.getMessage());

            }

            @Override
            public void onCompleted() {
                System.out.println("服务端响应结束。");

            }
        });

        for(int i=0; i<10; i++){
            //异步发送请求
            streamObserverRequest2.onNext(MyRequest.newBuilder().setCode(""+i).build());
        }
        //请求发送完成
        streamObserverRequest2.onCompleted();


        //由于客户端异步发送请求,因此需要阻塞线程等待服务端结果
        Thread.sleep(8000);
        //关闭channel通道
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }
}

最后分别启动服务器端和客户端即可看到效果,客户端代码可以分成4部分逐次运行,查看4中不同请求方式的调用结果。

总结:第一种方式可以用阻塞式的请求,但其他三种涉及到流的都必须使用非阻塞,这个调用关系gRPC已经在代码中强制给我们规范好了,在阻塞式的BlockingStub中将无法请求到流式的方法。特别是第三种方式也即是客户端流式RPC方式,请求一个流,响应一个非流的情况,我们必须在请求的流传入结束之后才能进行结果响应,否则会获得一个警告: Cancelling the stream with status Status{code=INTERNAL, description=Too many responses, cause=null},很明显提示Too many response,因为不是流式响应所以只能有一个Response,最终得到错误的结果。此文乃本人第一次学习gRPC之后整理的点东西,如有不当之处还请指教。

 

最后需要感谢张龙老师的课程,受益良多。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值