gRPC入门教程

gRPC

本博客源码地址: github.com/wz20/gprc-l...

一、gRPC 的基本概念

gRPC 简介

gRPC 是 Google 开源的一个高性能的 RPC(Remote Procedure Call) 框架。(由Google内部RPC项目Stubby演化而来,2015年正式开源)

主要使用场景:

  • 低延迟、高度可扩展的分布式系统。
  • 开发与云服务器通信的移动客户端。
  • 设计一个需要准确、高效且独立于语言的新协议。
  • 分层设计以实现扩展,例如。身份验证、负载平衡、日志记录和监控等。

核心设计思路:

要完成一个RPC通信的几个要素:

  1. 网络通信
  2. 协议
  3. 序列化
  4. 代理的创建
  • gRPC自己封装了网络通信的部分,提供了多种语言的网络通信封装。(C、Java[本质上使用Netty]、GO.....)
  • gRPC使用HTTP2协议。(传输数据的时候使用二进制数据内容、数据传输更快、支持双向流、连接的多路复用)
  • gRPC没有使用 XML 或者 JSON 这种文本格式,而是采用了基于 protocol buffers (google开源的一种序列化方式,在dubbo等中也能使用) 的二进制协议。
  • gRPC封装了代理stub。让调用者像调用本地方法那样调用远端的服务方法。

gRPC的好处:

  • 高效的进程间通信
  • 支持多种语言实现,原生支持C、GO、Java。
  • 支持多平台运行 Linux、Android、IOS、MacOS、Windows
  • 序列化方式采用protobuf,效率更高
  • 使用HTTP2协议
  • 生态丰富,大厂背书

HTTP2.0 介绍

HTTP1.x 协议

  • Http1.0协议 :请求响应的模式,短连接协议(无状态协议),传输数据文本结构,单工(无法实现服务端推送,只能采用客户端轮询的方式变相实现)
  • Http1.1协议 :请求响应的模式,有限的长连接(Keepalived)。如果使用webSocket方式可以实现双工,服务器向客户端推送。

总结:HTTP1.x的传输方式为文本格式,可读性更好,但是效率更差。本质上是无法实现双工通信(HTTP1.1本身不能实现)。

HTTP2.0 协议

  • 二进制协议,效率高,但可读性差

  • HTTP/2 使用头部压缩来减少传输数据的大小。这有助于减少网络带宽的使用量,并提高页面加载速度。

  • 多路复用,只通过一个 TCP连接就可以传输所有的请求数据。多路复用可以绕过浏览器限制同一个域名下的请求数量的问题,进而提高了网页的性能。

  • HTTP/2 允许服务器在不被请求的情况下主动向客户端发送数据。这有助于减少请求延迟,并提高页面加载速度。

HTTP2.0 协议的核心概念:

  1. 连接 Connection: 1 个 TCP 连接,包含一个或者多个 Stream。
  2. 数据流 Stream:一个双向通讯数据流,包含 1 条或者多条 Message。
  3. 消息 Message: 对应 HTTP/1 中的请求或者响应,包含一条或者多条 Frame。
  4. 数据帧 Frame:最小单位,以二进制压缩格式存放 HTTP/1 中的内容。

Tip:更多HTTP2.0知识可以查看:HTTP/2 协议(帧、消息、流简单的抓包分析)_http2 抓包-CSDN博客

Protocol Buffers

Protocol Buffers是一种与语言无关、与平台无关的可扩展机制,用于序列化结构化数据。

它就像 JSON 一样,除了它是 更小、更快,并且它会生成本地语言绑定。您定义如何 您希望您的数据一次结构化,然后您可以使用特殊生成的 用于轻松编写和读取各种结构化数据的源代码 数据流并使用多种语言。

protobuf 需要安装protobuf的编译器,编译器的目的就是把protobuf的IDL语言,转换成具体某一种开发语言。

Protobuf 环境准备

下载编译器:

Release Protocol Buffers v3.19.6 · protocolbuffers/protobuf (github.com)

安装(win):解压缩并配置环境变量

验证:protoc --version

IDEA插件安装

Protobuf 语法基础

  • 文件格式

    复制代码
    .proto
    ​
    UserService.proto
    xxxxx.proto
  • 版本设定 (第一行)

    ini 复制代码
    syntax = "proto3";
  • 注释

    arduino 复制代码
    //单行注释
    ​
    /* 多行注释 */
  • 与Java语言相关的语法

    ini 复制代码
    //后续protobuf生成的java代码是一个源文件还是多个源文件
    option java_multiple_files = false; //一个源文件
    ​
    //指定protobuf生成的类放置在哪个包中
    option java_package = "com.proto";
    ​
    //protobuf生成java外部类(仅用于管理内部类,内部类才是我们真正使用的)的名称
    option java_outer_classname = "ProtoOuter";
  • 逻辑包(Java工程师用的较少)

    go 复制代码
    //protobuf对于文件内容的管理
    package xxx;
  • 导入

    arduino 复制代码
    import "xxxx/UserService.proto"

Protobuf 核心语法

基本类型: Language Guide (proto 3) | Protocol Buffers Documentation (protobuf.dev)

枚举:

ini 复制代码
enum SEASON{
    SPRING = 0;
    SUMMER = 1;
}
//枚举的值 必须是0开始

消息:Message

ini 复制代码
message LoginRequest{
    string username = 1; //username字段的编号
    string password = 2;
    int32 age = 3;
}
  • 编号:从1开始,最大到2^29-1,注意:19000 - 19999 不可用,是protobuf自保留编号。

  • 关键字:

    • singular : 默认,代表这个字段的值只能包括0个或1个
    • repeated : 这个字段的返回值是多个,等价于Java中的List。
  • protobuf 中可以定义多个message

  • message 是可以嵌套的

    ini 复制代码
    message LoginRequest{
      message User{
        string name = 1;
        string password = 2;
      }
      
      string xxx = 1;
      int32  yyy = 2;
      User ppp = 3;
    }
    ​
    message Result{
      LoginRequest.User aaa = 1;
    }
  • oneof【其中一个】

    ini 复制代码
    message SimpleMessage{
    // test_oneOf 只能代表name 或 age 其中一个
      oneof test_oneOf{
        string name = 1;
        int32 age = 2;
      }
    }

Protobuf 服务定义

scss 复制代码
service HelloService{
  //定义若干个服务的方法,定义服务接口
  rpc hello(LoginRequest) returns(Result){};
}
  • 一个service可以定义多个服务方法。
  • 可以定义多个服务接口(service)
  • gRPC 服务有四种服务方式

二、gRPC开发实战

show me code, 下面写一个gRPC的hello world程序!!!

第一个gRPC应用

项目结构:

API 模块开发

  1. 编写 .proto 文件的IDL。

    ini 复制代码
    syntax = "proto3";
    ​
    option java_multiple_files = false;
    option java_package = "com.wz";
    option java_outer_classname = "HelloProto";
    ​
    /**
      IDL文件目的:发布RPC服务
     */
    message HelloRequest{
      string name = 1;
    }
    ​
    message HelloResponse{
      string result = 1;
    }
    ​
    service HelloService{
      rpc hello(HelloRequest) returns(HelloResponse){}
    }
  2. 通过protoc 命令将proto文件中的IDL 转换成编程语言

    ini 复制代码
    protoc --java_out = /xxx/xxx/xx.proto

    这种方式并不适用真实的开发环境,实战开发中,我们会采用maven的插件来进行编译并把他放置到具体位置。

  3. 配置maven插件

    grpc/grpc-java: The Java gRPC implementation. HTTP/2 based RPC (github.com)

    xml 复制代码
    ​
        <dependencies>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-netty-shaded</artifactId>
                <version>1.58.0</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-protobuf</artifactId>
                <version>1.58.0</version>
            </dependency>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-stub</artifactId>
                <version>1.58.0</version>
            </dependency>
            <dependency> <!-- necessary for Java 9+ -->
                <groupId>org.apache.tomcat</groupId>
                <artifactId>annotations-api</artifactId>
                <version>6.0.53</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    ​
        <build>
            <extensions>
                <extension>
                    <groupId>kr.motd.maven</groupId>
                    <artifactId>os-maven-plugin</artifactId>
                    <version>1.7.1</version>
                </extension>
            </extensions>
            <plugins>
                <plugin>
                    <groupId>org.xolstice.maven.plugins</groupId>
                    <artifactId>protobuf-maven-plugin</artifactId>
                    <version>0.6.1</version>
                    <configuration>
    <!--                    编译-->
                        <protocArtifact>com.google.protobuf:protoc:3.24.0:exe:${os.detected.classifier}</protocArtifact>
                        <pluginId>grpc-java</pluginId>
    <!--                    生成服务接口-->
                        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.58.0:exe:${os.detected.classifier}</pluginArtifact>
                    </configuration>
                    <executions>
                        <execution>
                            <goals>
                                <goal>compile</goal>
                                <goal>compile-custom</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    ​
  4. 执行maven插件

compile执行成功后,可以在target中找到对应的代码:复制到开发目录下即可。

执行compile-custom命令,可以看到对应的代码:复制到开发目录下即可。

优化:我们可以通过自定义输出目录

xml 复制代码
             <configuration>
<!--                    编译-->
                 <protocArtifact>com.google.protobuf:protoc:3.24.0:exe:${os.detected.classifier}</protocArtifact>
                 <pluginId>grpc-java</pluginId>
<!--                    生成服务接口-->
                 <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.58.0:exe:${os.detected.classifier}</pluginArtifact>
                 <outputDirectory>${basedir}/src/main/java</outputDirectory>
<!--                    追加生成,避免清除以前的代码-->
                 <clearOutputDirectory>false</clearOutputDirectory>
             </configuration>

至此,api相关开发已完成。

代码简略分析:

Service 模块开发

  1. 实现业务接口 (添加具体功能)
  2. 创建服务器 (Netty)
  1. 引入api

    xml 复制代码
        <dependencies>
            <dependency>
                <groupId>org.example</groupId>
                <artifactId>rpc-grpc-api</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
  2. 实现业务接口

    java 复制代码
    package com.wz.service;
    
    import com.wz.HelloProto;
    import com.wz.HelloServiceGrpc;
    import io.grpc.stub.StreamObserver;
    
    /**
     * @ClassName: HelloServiceImpl
     * @Description: 具体得逻辑
     * @Author: Ze WANG
     * @Date: 2023/10/9
     * @Version 1.0
     **/
    public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
        /**
         * grpc 的返回值并不是通过Java中的返回值来返回。而是通过观察者设计模式通过参数返回的,后续会有详细的解释
         * 1. 接收客户端提交的参数
         * 2. 业务处理
         * 3. 返回处理结果
         * @param request req
         * @param responseObserver 响应观察者
         */
        @Override
        public void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {
            //1.接收client的请求参数
            String name = request.getName();
            //2.业务处理
            System.out.println("service name===>"+name);
            //3.封装响应
            //3.1 创建响应对象的构造者
            HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();
            //3.2 填充数据
            builder.setResult("hello method invoke ok");
            //3.3 封装响应对象
            HelloProto.HelloResponse helloResponse = builder.build();
            //4.返回给客户端
            responseObserver.onNext(helloResponse);
            //5.通知客户端响应已结束
            responseObserver.onCompleted();
        }
    }
  3. 创建服务端

    java 复制代码
    package com.wz.server;
    
    import com.wz.service.HelloServiceImpl;
    import io.grpc.Server;
    import io.grpc.ServerBuilder;
    
    import java.io.IOException;
    
    /**
     * @ClassName: GrpcServer1
     * @Description: 服务端1
     * @Author: Ze WANG
     * @Date: 2023/10/9
     * @Version 1.0
     **/
    public class GrpcServer1 {
        public static void main(String[] args) throws InterruptedException, IOException {
            //1.设置端口
            ServerBuilder<?> serverBuilder = ServerBuilder.forPort(9000);
            //2.发布服务
            serverBuilder.addService(new HelloServiceImpl());
            //3.创建服务对象
            Server server = serverBuilder.build();
            //4.启动服务器
            server.start();
            server.awaitTermination();
    
        }
    }
  4. 启动服务器

Client 模块开发

  1. 通过代理对象完成远端对象的调用
java 复制代码
package com.wz.client;

import com.wz.HelloProto;
import com.wz.HelloServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

/**
 * @ClassName: GrpcClient1
 * @Description: 客户端
 * @Author: Ze WANG
 * @Date: 2023/10/9
 * @Version 1.0
 **/
public class GrpcClient1 {
    public static void main(String[] args) {
        //1.创建通信的管道
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();
        try {
            //2.获得代理对象 stub
            HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub = HelloServiceGrpc.newBlockingStub(managedChannel);
            //3.完成rpc调用
            //3.1 准备参数
            HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
            builder.setName("WangZe");
            HelloProto.HelloRequest helloRequest = builder.build();
            //3.2 rpc请求
            HelloProto.HelloResponse helloResponse = helloServiceBlockingStub.hello(helloRequest);
            //3.3 获取返回值
            String result = helloResponse.getResult();
            System.out.println("rpc响应内容==》"+result);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            managedChannel.shutdown();
        }
    }
}
package com.wz.client;

import com.wz.HelloProto;
import com.wz.HelloServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

/**
 * @ClassName: GrpcClient1
 * @Description: 客户端
 * @Author: Ze WANG
 * @Date: 2023/10/9
 * @Version 1.0
 **/
public class GrpcClient1 {
    public static void main(String[] args) {
        //1.创建通信的管道
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();
        try {
            //2.获得代理对象 stub
            HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub = HelloServiceGrpc.newBlockingStub(managedChannel);
            //3.完成rpc调用
            //3.1 准备参数
            HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
            builder.setName("WangZe");
            HelloProto.HelloRequest helloRequest = builder.build();
            //3.2 rpc请求
            HelloProto.HelloResponse helloResponse = helloServiceBlockingStub.hello(helloRequest);
            //3.3 记牌器瞎忙活忙活没人品冒号
            String result = helloResponse.getResult();
            System.out.println("rpc响应内容==》"+result);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            managedChannel.shutdown();
        }
    }
}

启动客户端后,观察客户端的与服务端的打印:

Client:

Service:

注意事项:

gRPC的四种通信方式

四种通信方式:

  • 简单RPC(一元RPC) Unary RPC
  • 服务端流式RPC Server Streaming RPC
  • 客户端流式RPC Client Streaming RPC
  • 双向流RPC Bi-directional Stream RPC

简单RPC

我们第一个gRPC应用就是使用的这种RPC,简单RPC的特点是:请求-响应式

当客户端发起调用后,会提交数据并等待服务端响应。

语法:

scss 复制代码
service HelloService{
  rpc hello(HelloRequest) returns(HelloResponse){}
}

服务端流式RPC

概念:一个请求对象,服务端可以回传多个结果对象。多个结果往往不是一起返回的

使用场景: 服务端需要源源不断的给客户端返回数据。

  • 交易所的k线图:

    lua 复制代码
          股票标号
    client -----> Server
          <-----
          某一个时刻的股票行情
          <-----
          另外一个时刻的股票行情

语法:

scss 复制代码
service HelloService{
  rpc hello(HelloRequest) returns(stream HelloResponse){}
}

show me code

  1. 在proto文件中添加一个service方法,并生成对应的Java代码:

    scss 复制代码
    service HelloService{
      rpc hello(HelloRequest) returns(HelloResponse){}
      rpc c2ss(HelloRequest) returns(stream HelloResponse){}
    }
  2. 在service中实现业务逻辑

    java 复制代码
        @Override
        public void c2ss(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {
            //1.接收client的请求参数
            String name = request.getName();
            //2.业务处理
            System.out.println("service param name===>"+name);
            //3.封装响应
            for (int i = 0; i < 10; i++) {
                try {
                    //3.1 创建响应对象的构造者
                    HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();
                    //3.2 填充数据
                    builder.setResult("处理结果==>" + i);
                    //3.3 封装响应对象
                    HelloProto.HelloResponse helloResponse = builder.build();
                    //4.返回给客户端
                    responseObserver.onNext(helloResponse);
                    //模拟时间间隔
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            //5.通知客户端响应已结束
            responseObserver.onCompleted();
        }
  3. 服务发布(因为我们是在HelloServiceImpl中实现的,所以不需要再次发布了)

    arduino 复制代码
    public class GrpcServer1 {
        public static void main(String[] args) throws InterruptedException, IOException {
            //1.设置端口
            ServerBuilder<?> serverBuilder = ServerBuilder.forPort(9000);
            //2.发布服务
            serverBuilder.addService(new HelloServiceImpl());
            //3.创建服务对象
            Server server = serverBuilder.build();
            //4.启动服务器
            server.start();
            server.awaitTermination();
        }
    }
  4. 编写客户端

    java 复制代码
    package com.wz.client;
    ​
    import com.wz.HelloProto;
    import com.wz.HelloServiceGrpc;
    import io.grpc.ManagedChannel;
    import io.grpc.ManagedChannelBuilder;
    ​
    import java.util.Iterator;
    ​
    /**
     * @ClassName: GrpcClient2
     * @Description: 客户端,用于测试服务端流式rpc
     * @Author: Ze WANG
     * @Date: 2023/10/9
     * @Email: Ze_Wang2@human-horizons.com
     * @Version 1.0
     **/
    public class GrpcClient2 {
        public static void main(String[] args) {
            //1.创建通信的管道
            ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();
            try {
                //2.获得代理对象 stub
                HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub = HelloServiceGrpc.newBlockingStub(managedChannel);
                //3.完成rpc调用
                //3.1 准备参数
                HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
                builder.setName("WangZe");
                HelloProto.HelloRequest helloRequest = builder.build();
                //3.2 rpc请求
                Iterator<HelloProto.HelloResponse> helloResponseIterator = helloServiceBlockingStub.c2ss(helloRequest);
                //3.3 获取返回值
                while (helloResponseIterator.hasNext()){
                    HelloProto.HelloResponse helloResponse = helloResponseIterator.next();
                    System.out.println("helloResponse.getResult()"+helloResponse.getResult());
                }
    ​
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                managedChannel.shutdown();
            }
        }
    }
    ​
  5. 启动客户端观察返回:

Tips: 这里我们使用的阻塞(接收所有的message才继续处理)的监听方式,我们实战中客户端不应该一直阻塞,而是应该使用异步监听(来一个message就处理一个)

监听-异步方式 处理服务端流式RPC开发

修改客户端的逻辑:

csharp 复制代码
public class GrpcClient3 {
    /**
     * 用于存储响应消息
     */
    static List<HelloProto.HelloResponse> responses = new ArrayList<>();

    public static void main(String[] args) {
        //1.创建通信的管道
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();


        try {
            //2.获得代理对象 stub
            HelloServiceGrpc.HelloServiceStub helloServiceStub = HelloServiceGrpc.newStub(managedChannel);
            //3.完成rpc调用
            //3.1 准备参数
            HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
            builder.setName("WangZe");
            HelloProto.HelloRequest helloRequest = builder.build();
            //3.2 rpc请求
           helloServiceStub.c2ss(helloRequest, new StreamObserver<HelloProto.HelloResponse>() {
               //3.3 响应的监听,根据不同的时间做不同的处理 onNext() onError() onCompleted()
               @Override
                public void onNext(HelloProto.HelloResponse value) {
                    //监听消息
                   System.out.println("服务端发来了一个消息:"+value);
                   responses.add(value);
                }

                @Override
                public void onError(Throwable t) {
                    //监听异常
                    System.out.println("ERROR:"+t.getMessage());
                }

                @Override
                public void onCompleted() {
                    //把服务端所有的数据拿到后,再进行业务处理,在此处写
                    System.out.println("所有消息集合:");
                    responses.forEach(System.out::println);
                }
            });


        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            managedChannel.shutdown();
            System.out.println("client处理结束......");
        }
    }
}

注意:客户端没有阻塞,所以拿到一个消息后执行完逻辑进程就关闭了。

优化后的逻辑: 对客户端添加等待时间

java 复制代码
package com.wz.client;
​
import com.wz.HelloProto;
import com.wz.HelloServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.ClientResponseObserver;
import io.grpc.stub.StreamObserver;
​
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
​
/**
 * @ClassName: GrpcClient3
 * @Description: 异步
 * @Author: Ze WANG
 * @Date: 2023/10/13
 * @Email: Ze_Wang2@human-horizons.com
 * @Version 1.0
 **/
public class GrpcClient3 {
    /**
     * 用于存储响应消息
     */
    static List<HelloProto.HelloResponse> responses = new ArrayList<>();
​
    public static void main(String[] args) {
        //1.创建通信的管道
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();
​
​
        try {
            //2.获得代理对象 stub
            HelloServiceGrpc.HelloServiceStub helloServiceStub = HelloServiceGrpc.newStub(managedChannel);
            //3.完成rpc调用
            //3.1 准备参数
            HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
            builder.setName("WangZe");
            HelloProto.HelloRequest helloRequest = builder.build();
            //3.2 rpc请求
           helloServiceStub.c2ss(helloRequest, new StreamObserver<HelloProto.HelloResponse>() {
               //3.3 响应的监听,根据不同的时间做不同的处理 onNext() onError() onCompleted()
               @Override
                public void onNext(HelloProto.HelloResponse value) {
                    //监听消息
                   System.out.println("服务端发来了一个消息:"+value);
                   responses.add(value);
                }
​
                @Override
                public void onError(Throwable t) {
                    //监听异常
                    System.out.println("ERROR:"+t.getMessage());
                }
​
                @Override
                public void onCompleted() {
                    //把服务端所有的数据拿到后,再进行业务处理,在此处写
                    System.out.println("所有消息集合:");
                    responses.forEach(System.out::println);
                }
            });
           //设置等待时间
            managedChannel.awaitTermination(60, TimeUnit.SECONDS);
​
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            managedChannel.shutdown();
            System.out.println("client处理结束......");
        }
    }
}
​

运行结果:

vbnet 复制代码
服务端发来了一个消息:result: "Result==>0"

服务端发来了一个消息:result: "Result==>1"

服务端发来了一个消息:result: "Result==>2"

服务端发来了一个消息:result: "Result==>3"

服务端发来了一个消息:result: "Result==>4"

服务端发来了一个消息:result: "Result==>5"

服务端发来了一个消息:result: "Result==>6"

服务端发来了一个消息:result: "Result==>7"

服务端发来了一个消息:result: "Result==>8"

服务端发来了一个消息:result: "Result==>9"

所有消息集合:
result: "Result==>0"

result: "Result==>1"

result: "Result==>2"

result: "Result==>3"

result: "Result==>4"

result: "Result==>5"

result: "Result==>6"

result: "Result==>7"

result: "Result==>8"

result: "Result==>9"

客户端流式RPC

概念:客户端发送多个请求对象,服务端只返回一个结果。

应用场景:IOT(物联网)

例如车上的传感器,源源不断的将车辆信号数据发送到服务端。服务端不需要一直响应给车。

编程语法:protobuf

scss 复制代码
service HelloService{
  rpc cs2s(stream HelloRequest) returns(HelloResponse){}
}

show me Code

  1. 编写protobuf 并生成API

    ini 复制代码
    syntax = "proto3";
    ​
    option java_multiple_files = false;
    option java_package = "com.wz";
    option java_outer_classname = "HelloProto";
    ​
    /**
      IDL文件目的:发布RPC服务
     */
    message HelloRequest{
      string name = 1;
    }
    ​
    message HelloResponse{
      string result = 1;
    }
    ​
    service HelloService{
      rpc hello(HelloRequest) returns(HelloResponse){}
      rpc c2ss(HelloRequest) returns(stream HelloResponse){}
      rpc cs2s(stream HelloRequest) returns(HelloResponse){}
    }
    ​
  2. 开发服务端

    typescript 复制代码
        /**
         * 客户端流式RPC服务端
         * @param responseObserver 返回responseObserver
         * @return 监控请求消息 StreamObserver
         */
        @Override
        public StreamObserver<HelloProto.HelloRequest> cs2s(StreamObserver<HelloProto.HelloResponse> responseObserver) {
            return new StreamObserver<HelloProto.HelloRequest>() {
                @Override
                public void onNext(HelloProto.HelloRequest value) {
                    //1.监控每一个client发送的消息
                    System.out.println("接收到客户端消息:"+value);
                }
    ​
                @Override
                public void onError(Throwable t) {
                    //2.监控异常
                    System.out.println("监听到异常:"+t.getMessage());
                }
    ​
                @Override
                public void onCompleted() {
                    //3.全部消息是否发送完整
                    System.out.println("客户端消息全部接收成功");
                    //响应一个全部信息
                    HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();
                    builder.setResult("all message is ok");
                    HelloProto.HelloResponse helloResponse = builder.build();
                    responseObserver.onNext(helloResponse);
                    responseObserver.onCompleted();
                }
            };
        }
  3. 开发客户端:

    java 复制代码
    package com.wz.client;
    ​
    import com.wz.HelloProto;
    import com.wz.HelloServiceGrpc;
    import io.grpc.ManagedChannel;
    import io.grpc.ManagedChannelBuilder;
    import io.grpc.stub.StreamObserver;
    ​
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    ​
    /**
     * @ClassName: GrpcClient4
     * @Description: 客户端流式RPC 客户端
     * @Author: Ze WANG
     * @Date: 2023/10/13
     * @Email: Ze_Wang2@human-horizons.com
     * @Version 1.0
     **/
    public class GrpcClient4 {
        /**
         * 用于存储响应消息
         */
        static List<HelloProto.HelloResponse> responses = new ArrayList<>();
    ​
        public static void main(String[] args) {
            //1.创建通信的管道
            ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();
    ​
    ​
            try {
                //2.获得代理对象 stub
                HelloServiceGrpc.HelloServiceStub helloServiceStub = HelloServiceGrpc.newStub(managedChannel);
                //3.完成rpc调用
                StreamObserver<HelloProto.HelloRequest> helloRequestStreamObserver = helloServiceStub.cs2s(new StreamObserver<HelloProto.HelloResponse>() {
                    @Override
                    public void onNext(HelloProto.HelloResponse value) {
                        System.out.println("监听到服务端返回:"+value);
                    }
    ​
                    @Override
                    public void onError(Throwable t) {
    ​
                    }
    ​
                    @Override
                    public void onCompleted() {
                        System.out.println("服务端响应完毕");
                    }
                });
                for (int i = 0; i < 10; i++) {
                    HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
                    builder.setName("WangZe"+i);
                    HelloProto.HelloRequest helloRequest = builder.build();
                    //发送消息
                    helloRequestStreamObserver.onNext(helloRequest);
                    //睡眠
                    Thread.sleep(1000);
                }
                //发送结束
                helloRequestStreamObserver.onCompleted();
    ​
                managedChannel.awaitTermination(60,TimeUnit.SECONDS);
    ​
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                managedChannel.shutdown();
            }
        }
    }
    ​
  4. 运行结果

    less 复制代码
    Client:
    已连接到目标 VM, 地址: ''127.0.0.1:56552',传输: '套接字''
    监听到服务端返回:result: "all message is ok"
    ​
    服务端响应完毕
    ​
    ​
    Server:
    接收到客户端消息:name: "WangZe0"
    ​
    接收到客户端消息:name: "WangZe1"
    ​
    接收到客户端消息:name: "WangZe2"
    ​
    接收到客户端消息:name: "WangZe3"
    ​
    接收到客户端消息:name: "WangZe4"
    ​
    接收到客户端消息:name: "WangZe5"
    ​
    接收到客户端消息:name: "WangZe6"
    ​
    接收到客户端消息:name: "WangZe7"
    ​
    接收到客户端消息:name: "WangZe8"
    ​
    接收到客户端消息:name: "WangZe9"
    ​
    客户端消息全部接收成功
    ​

双向流RPC

概念: 客户端可以发送多个请求消息,服务端也可以响应多个消息

应用场景: 聊天室

编码:

scss 复制代码
rpc cs2ss(stream HelloRequest) returns(stream HelloResponse){}

show me code

  1. api

    ini 复制代码
    syntax = "proto3";
    ​
    option java_multiple_files = false;
    option java_package = "com.wz";
    option java_outer_classname = "HelloProto";
    ​
    /**
      IDL文件目的:发布RPC服务
     */
    message HelloRequest{
      string name = 1;
    }
    ​
    message HelloResponse{
      string result = 1;
    }
    ​
    service HelloService{
      rpc hello(HelloRequest) returns(HelloResponse){}
      rpc c2ss(HelloRequest) returns(stream HelloResponse){}
      rpc cs2s(stream HelloRequest) returns(HelloResponse){}
      rpc cs2ss(stream HelloRequest) returns(stream HelloResponse){}
    }
    ​
  2. 开发服务端

    typescript 复制代码
        @Override
        public StreamObserver<HelloProto.HelloRequest> cs2ss(StreamObserver<HelloProto.HelloResponse> responseObserver) {
            return new StreamObserver<HelloProto.HelloRequest>() {
                @Override
                public void onNext(HelloProto.HelloRequest value) {
                    //1.监控每一个client发送的消息
                    System.out.println("接收到客户端消息:"+value);
                    //=====每次都返回=====
                    HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();
                    builder.setResult("message is ok");
                    HelloProto.HelloResponse helloResponse = builder.build();
                    responseObserver.onNext(helloResponse);
                }
    ​
                @Override
                public void onError(Throwable t) {
                    //2.监控异常
                    System.out.println("监听到异常:"+t.getMessage());
                }
    ​
                @Override
                public void onCompleted() {
                    //3.全部消息是否发送完整
                    System.out.println("客户端消息全部接收成功");
                    //响应一个全部信息
                    HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();
                    builder.setResult("all message is ok");
                    HelloProto.HelloResponse helloResponse = builder.build();
                    responseObserver.onNext(helloResponse);
                    responseObserver.onCompleted();
                }
            };
        }
  3. 开发客户端

    java 复制代码
    package com.wz.client;
    ​
    import com.wz.HelloProto;
    import com.wz.HelloServiceGrpc;
    import io.grpc.ManagedChannel;
    import io.grpc.ManagedChannelBuilder;
    import io.grpc.stub.StreamObserver;
    ​
    import java.util.concurrent.TimeUnit;
    ​
    /**
     * @ClassName: GrpcClient5
     * @Description: 双向流客户端
     * @Author: Ze WANG
     * @Date: 2023/10/13
     * @Email: Ze_Wang2@human-horizons.com
     * @Version 1.0
     **/
    public class GrpcClient5 {
        public static void main(String[] args) {
            //1.创建通信的管道
            ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();
    ​
    ​
            try {
                //2.获得代理对象 stub
                HelloServiceGrpc.HelloServiceStub helloServiceStub = HelloServiceGrpc.newStub(managedChannel);
                //3.完成rpc调用
                StreamObserver<HelloProto.HelloRequest> helloRequestStreamObserver = helloServiceStub.cs2ss(new StreamObserver<HelloProto.HelloResponse>() {
                    @Override
                    public void onNext(HelloProto.HelloResponse value) {
                        System.out.println("监听到服务端返回:"+value);
                    }
    ​
                    @Override
                    public void onError(Throwable t) {
    ​
                    }
    ​
                    @Override
                    public void onCompleted() {
                        System.out.println("服务端响应完毕");
                    }
                });
                for (int i = 0; i < 10; i++) {
                    HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
                    builder.setName("WangZe"+i);
                    HelloProto.HelloRequest helloRequest = builder.build();
                    //发送消息
                    helloRequestStreamObserver.onNext(helloRequest);
                    //睡眠
                    Thread.sleep(1000);
                }
                //发送结束
                helloRequestStreamObserver.onCompleted();
    ​
                managedChannel.awaitTermination(60, TimeUnit.SECONDS);
    ​
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                managedChannel.shutdown();
            }
        }
    }
    ​
  4. 运行结果

    sql 复制代码
    client:
    已连接到目标 VM, 地址: ''127.0.0.1:57026',传输: '套接字''
    监听到服务端返回:result: "message is ok"
    ​
    监听到服务端返回:result: "message is ok"
    ​
    监听到服务端返回:result: "message is ok"
    ​
    监听到服务端返回:result: "message is ok"
    ​
    监听到服务端返回:result: "message is ok"
    ​
    监听到服务端返回:result: "message is ok"
    ​
    监听到服务端返回:result: "message is ok"
    ​
    监听到服务端返回:result: "message is ok"
    ​
    监听到服务端返回:result: "message is ok"
    ​
    监听到服务端返回:result: "message is ok"
    ​
    监听到服务端返回:result: "all message is ok"
    ​
    服务端响应完毕
    ​
    ======================================================
    server:
    已连接到目标 VM, 地址: ''127.0.0.1:56974',传输: '套接字''
    接收到客户端消息:name: "WangZe0"
    ​
    接收到客户端消息:name: "WangZe1"
    ​
    接收到客户端消息:name: "WangZe2"
    ​
    接收到客户端消息:name: "WangZe3"
    ​
    接收到客户端消息:name: "WangZe4"
    ​
    接收到客户端消息:name: "WangZe5"
    ​
    接收到客户端消息:name: "WangZe6"
    ​
    接收到客户端消息:name: "WangZe7"
    ​
    接收到客户端消息:name: "WangZe8"
    ​
    接收到客户端消息:name: "WangZe9"
    ​
    客户端消息全部接收成功
    ​

gRPC代理方式总结

  • BlockingStub

    阻塞 通信方式

  • Stub

    异步 通过监听处理

  • FutureStub

    同步 异步 NettyFuture

    FutureStub只能应用于 简单RPC

FutureStub 案例:

1.首先创建一个新的proto文件,并生成API

ini 复制代码
syntax = "proto3";
​
option java_multiple_files = false;
option java_package = "com.wz";
option java_outer_classname = "TestProto";
​
message TestRequest{
  string name = 1;
}
​
message TestResponse{
  string result = 1;
}
​
service TestService{
  rpc testWz(TestRequest) returns(TestResponse){}
}
​
​

2.开发服务端Impl

scala 复制代码
public class TestServiceImpl extends TestServiceGrpc.TestServiceImplBase {
    @Override
    public void testWz(TestProto.TestRequest request, StreamObserver<TestProto.TestResponse> responseObserver) {
        String name = request.getName();
        System.out.println("name:"+name);
        //响应
        responseObserver.onNext(TestProto.TestResponse.newBuilder().setResult("test is ok").build());
        responseObserver.onCompleted();
    }
}
arduino 复制代码
public class GrpcServer1 {
    public static void main(String[] args) throws InterruptedException, IOException {
        //1.设置端口
        ServerBuilder<?> serverBuilder = ServerBuilder.forPort(9000);
        //2.发布服务
        serverBuilder.addService(new HelloServiceImpl());
        serverBuilder.addService(new TestServiceImpl());
        //3.创建服务对象
        Server server = serverBuilder.build();
        //4.启动服务器
        server.start();
        server.awaitTermination();
    }
}

3.开发客户端

  • 同步

    java 复制代码
    package com.wz.client;
    ​
    import com.google.common.util.concurrent.ListenableFuture;
    import com.wz.HelloProto;
    import com.wz.HelloServiceGrpc;
    import com.wz.TestProto;
    import com.wz.TestServiceGrpc;
    import io.grpc.ManagedChannel;
    import io.grpc.ManagedChannelBuilder;
    ​
    /**
     * @ClassName: GrpcClientFutureStub
     * @Description: FutureStub
     * @Author: Ze WANG
     * @Date: 2023/10/13
     * @Email: Ze_Wang2@human-horizons.com
     * @Version 1.0
     **/
    public class GrpcClientFutureStub {
        public static void main(String[] args) {
            //1.创建通信的管道
            ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();
            try {
                TestServiceGrpc.TestServiceFutureStub testServiceFutureStub = TestServiceGrpc.newFutureStub(managedChannel);
                ListenableFuture<TestProto.TestResponse> future = testServiceFutureStub.testWz(TestProto.TestRequest.newBuilder().setName("wangze").build());
                //同步
                TestProto.TestResponse testResponse = future.get();
                System.out.println(testResponse);
                System.out.println("后续操作...........");
                
    ​
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                managedChannel.shutdown();
            }
        }
    }
    ​
  • 异步:

    typescript 复制代码
    public class GrpcClientFutureStub {
        public static void main(String[] args) {
            //1.创建通信的管道
            ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();
            try {
                TestServiceGrpc.TestServiceFutureStub testServiceFutureStub = TestServiceGrpc.newFutureStub(managedChannel);
                ListenableFuture<TestProto.TestResponse> future = testServiceFutureStub.testWz(TestProto.TestRequest.newBuilder().setName("wangze").build());
                //异步
                Futures.addCallback(future, new FutureCallback<TestProto.TestResponse>() {
                    @Override
                    public void onSuccess(TestProto.TestResponse result) {
                        //成功的情况
                        System.out.println(result);
                    }
    ​
                    @Override
                    public void onFailure(Throwable t) {
                        //异常错误
                        System.out.println(t.getMessage());
                    }
                }, Executors.newCachedThreadPool());
    ​
                System.out.println("后续操作.......");
                managedChannel.awaitTermination(20, TimeUnit.SECONDS);
    ​
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                managedChannel.shutdown();
            }
        }
    }

如果想用SpringBoot整合grpc,可以看看yidongnan/grpc-spring-boot-starter: Spring Boot starter module for gRPC framework. (github.com)

相关推荐
带刺的坐椅15 分钟前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看2 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程2 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t2 小时前
ZIP工具类
java·zip
lang201509282 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan3 小时前
第10章 Maven
java·maven
百锦再4 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说4 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多4 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring
百锦再4 小时前
对前后端分离与前后端不分离(通常指服务端渲染)的架构进行全方位的对比分析
java·开发语言·python·架构·eclipse·php·maven