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)

相关推荐
blammmp8 分钟前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵27 分钟前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong31 分钟前
Java反射
java·开发语言·反射
九圣残炎1 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge1 小时前
Netty篇(入门编程)
java·linux·服务器
Re.不晚2 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐2 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven
码农派大星。2 小时前
Spring Boot 配置文件
java·spring boot·后端
顾北川_野2 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
江深竹静,一苇以航2 小时前
springboot3项目整合Mybatis-plus启动项目报错:Invalid bean definition with name ‘xxxMapper‘
java·spring boot