gRPC构建RPC服务

本文介绍如何使用Grpc来否件和发布RPC服务,本文在最后附上github地址。

1.下载&安装 java版本的protoc,下载地址

我这里使用的是3.3.0版本,如图所示


protoc-version.png

选择一个属于你的平台protoc来安装,安装很简单,只需要将下载好的protoc的zip包解压到指定目录即可。

2.创建maven工程

选择一款你喜欢的编译器创建一个maven工程,maven工程创建好之后,在其工程的根目录下的pom文件中添加如下内容:

pom.xml文件内容:

<properties>
    <grpc.version>1.6.1</grpc.version>
    <jdk.version>1.8</jdk.version>
    <protoc.version>3.3.0</protoc.version>
    <kr.motd.version>1.5.0.Final</kr.motd.version>
</properties>

<dependencies>
      <!--grpc工程依赖-->
       <dependency>
           <groupId>io.grpc</groupId>
           <artifactId>grpc-netty</artifactId>
           <version>${grpc.version}</version>
       </dependency>
       <dependency>
           <groupId>io.grpc</groupId>
           <artifactId>grpc-protobuf</artifactId>
           <version>${grpc.version}</version>
       </dependency>
       <dependency>
           <groupId>io.grpc</groupId>
           <artifactId>grpc-stub</artifactId>
           <version>${grpc.version}</version>
       </dependency>
</dependencies>

<build>
       <extensions>
           <extension>
               <groupId>kr.motd.maven</groupId>
               <artifactId>os-maven-plugin</artifactId>
               <version>${kr.motd.version}</version>
           </extension>
       </extensions>

       <plugins>
           <!-- protobuf插件 -->
           <plugin>
               <groupId>org.xolstice.maven.plugins</groupId>
               <artifactId>protobuf-maven-plugin</artifactId>
               <version>0.5.0</version>
               <!--在命令行直接编译即可-->
               <configuration>
                   <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
                   <pluginId>grpc-java</pluginId>
                   <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
                   </pluginArtifact>
                   <!--protobuf文件路径,在这里我的proto3规则文件放在src/main/proto下-->
                   <protoSourceRoot>src/main/proto</protoSourceRoot>
                   <!--protoc可执行文件据对路径,在第一步中的下载的protoc安装包解压后得到的,这是我的安装路径-->
                   <protocExecutable>/Volumes/NETAC/soft/dev/protoc/bin/protoc</protocExecutable>
               </configuration>
               <executions>
                   <execution>
                       <goals>
                           <goal>compile</goal>
                           <goal>compile-custom</goal>
                       </goals>
                   </execution>
               </executions>
           </plugin>

           <!--jdk插件-->
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-compiler-plugin</artifactId>
               <configuration>
                   <source>${jdk.version}</source>
                   <target>${jdk.version}</target>
               </configuration>
           </plugin>
       </plugins>
</build>

3.创建proto规则文件

在工程目录下的src/main下创建proto规则文件目录,目录结构如图所示:
proto_dir.png

3.1.创建规则文件

在这里我的proto规则文件中分别定义了Account和Greeter两个服务接口。

  • Greeter服务接口中定义了SayHello一个方法。
    在Greeter服务中的方法只是简单的打招呼方法。

  • Account服务接口中定义了addAccount和getAccoutByName两个方法。
    在Account服务中的这两个方法功能是模拟添加和查询账户。

3.1.1.定义服务

在创建好的proto目录下创建hello_account.proto规则文件,用于定义rpc服务,内容如下:

//使用proto3语法
syntax = "proto3";

//指定proto文件包名
package org.cooze.grpc.service;

//指定 java 包名
option java_package = "org.cooze.grpc.service";
//指定proto文件生成java文件后的类名
option java_outer_classname = "ServiceProto";

//开启多文件
option java_multiple_files = true;

//倒入指定的.proto文件
import "entity/req_res.proto";

//定义rpc服务接口
service Greeter {
  //服务端接口方法
  rpc SayHello (org.cooze.grpc.entity.HelloRequest) returns (org.cooze.grpc.entity.HelloReply);
}

//定义rpc服务接口
service Account {
   rpc addAccount(org.cooze.grpc.entity.Account) returns (org.cooze.grpc.entity.AccountResponse);
   rpc getAccoutByName(org.cooze.grpc.entity.Account) returns (org.cooze.grpc.entity.AccountResponse);
}

3.1.2.定义消息

在proto目录下的子目录entity中创建req_res.proto文件,用于定义rpc的消息类型,内容如下:

//使用proto3语法
syntax = "proto3";

//指定proto文件包名
package org.cooze.grpc.entity;

//指定 java 包名
option java_package = "org.cooze.grpc.entity";
//指定proto文件生成java文件后的类名
option java_outer_classname = "EntityProto";

//开启多文件
option java_multiple_files = true;

//请求参数
message HelloRequest {
  string name = 1;
}

//响应参数
message HelloReply {
  string message = 1;
}

message Account {
    string name = 1;
    string sex = 2;
    int32 age = 3;
}

message AccountResponse {
    string msg = 1;
    int32 code = 2;
    repeated Account results = 3;
}

proto3定义服务和消息,请参看我翻译的《gRPC之proto语法》,翻译难免带有个人主观色彩,请见谅.

3.2.编译proto文件

在创建好proto文件,并定义好消息类型和服务之后,结下来就是编译proto规则文件生成对应的java代码。

打开命令终端并切换到工程根目录下
我这里使用的编译器是idea编译器,所以打开终端很简单-_-!,终端打开后键入命令mvn compile编译即可,如图 :

compile.png

在执行完比那一命令之后,在工程根目录下会生成的target,而target目录的结构以及java类文件,如下图所示:


generator_code.png

其中,生成的服务定义接口类为:AccountGrpc和GreeterGrpc,消息类型类为:Account、AccountResponse、HelloReply、HelloRequest

4.Grpc服务和客户端实现

完成编辑proto规则文件和生成对应待grpc代码之后,就可以开始实现Grpc的服务端代码和客户端代码了。
创建java包结构和文件,如图所示:


create_package.png

4.1.Grpc服务端业务实现代码

不多说啥了,代码里有注释,所以上代码!

GreeterImpl.java代码如下:

public class GreeterImpl extends GreeterGrpc.GreeterImplBase {

    @Override
    public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
        HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();

        //处理接收到的消息
        String msg = reply.getMessage();
        System.out.println("服务端收到消息:" + msg);

        //响应消息
        HelloReply response = reply.toBuilder().setMessage("世界你好!").build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();

    }
}

AccountImpl.java代码如下:

public class AccountImpl extends AccountGrpc.AccountImplBase {

    @Override
    public void addAccount(Account request, StreamObserver<AccountResponse> responseObserver) {
        //处理请求参数
        System.out.println(StringFormatter.format("新增用户:%s\n性别:%s\n年龄:%d岁", request.getName(), request.getSex(), request.getAge()).get());

        //处理响应参数
        AccountResponse response = AccountResponse.getDefaultInstance().toBuilder()
                .setCode(10000)
                .setMsg("success!").build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }

    @Override
    public void getAccoutByName(Account request, StreamObserver<AccountResponse> responseObserver) {
        //处理请求参数
        System.out.println(StringFormatter.format("请求查询用户名:%s", request.getName()).get());

        //处理响应参数
        List<Account> list = new ArrayList<>();
        Account account1 = Account.getDefaultInstance().toBuilder()
                .setName("张三")
                .setAge(20)
                .setSex("男").build();
        list.add(account1);

        Account account2 = Account.getDefaultInstance().toBuilder()
                .setAge(30)
                .setSex("男")
                .setName("李四").build();

        list.add(account2);

        AccountResponse response = AccountResponse.getDefaultInstance().toBuilder()
                .setCode(10000)
                .setMsg("success!")
                .addAllResults(list)
                .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

4.2.Grpc客户端代码

不多说啥了,代码里有注释,所以上代码!

BaseClient.java代码如下:

public class BaseClient {

    private final ManagedChannel channel;
    private final GreeterGrpc.GreeterBlockingStub greeterBlockingStub;
    private final AccountGrpc.AccountBlockingStub accountBlockingStub;

    private BaseClient(ManagedChannel channel) {
        this.channel = channel;
        this.greeterBlockingStub = GreeterGrpc.newBlockingStub(channel);
        this.accountBlockingStub = AccountGrpc.newBlockingStub(channel);
    }

    /**
     * 构造客户端与Greeter 服务端连接 {@code host:port}
     *
     * @param host 主机地址
     * @param port 端口
     */
    public BaseClient(String host, int port) {
        this(ManagedChannelBuilder.forAddress(host, port)
                // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
                // needing certificates.
                .usePlaintext(true)
                .build());
    }

    /**
     * 关闭函数
     * @throws InterruptedException
     */
    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    public GreeterGrpc.GreeterBlockingStub getGreeterBlockingStub() {
        return greeterBlockingStub;
    }

    public AccountGrpc.AccountBlockingStub getAccountBlockingStub() {
        return accountBlockingStub;
    }
}

GreeterClient.java代码如下:

public class GreeterClient {

    private final GreeterGrpc.GreeterBlockingStub blockingStub;


    public GreeterClient(BaseClient client) {
        blockingStub = client.getGreeterBlockingStub();
    }

    public void greet(String name) {
        HelloRequest request = HelloRequest.newBuilder().setName(name).build();
        HelloReply response;
        try {
            response = blockingStub.sayHello(request);
            String msg = response.getMessage();
            //接收到服务端返回的消息
            System.out.println("客户端收到消息:" + msg);
        } catch (StatusRuntimeException e) {
            return;
        }
    }
}

AccountClient.java代码如下:

public class AccountClient {

    private final AccountGrpc.AccountBlockingStub accountBlockingStub;

    private final BaseClient client;

    public AccountClient(BaseClient client) {
        this.client = client;
        this.accountBlockingStub = client.getAccountBlockingStub();
    }

    public void addAccount(String name, String sex, int age) {

        Account account = Account.getDefaultInstance().toBuilder()
                .setName(name)
                .setSex(sex)
                .setAge(age)
                .build();

        AccountResponse response = this.accountBlockingStub.addAccount(account);

        System.out.println(StringFormatter.format("返回消息:%s\n状态:%d", response.getMsg(), response.getCode()).get());
    }

    public void queryAccout(String name) {
        Account account = Account.getDefaultInstance().toBuilder()
                .setName(name).build();
        AccountResponse response = this.accountBlockingStub.getAccoutByName(account);

        System.out.println(StringFormatter.format("返回消息:%s\n状态:%d", response.getMsg(), response.getCode()).getValue());
        System.out.println("查询结果:");
        List<Account> list = response.getResultsList();
        for (Account acc : list) {
            System.out.println(StringFormatter.format("姓名:%s,性别:%s,年龄:%d", acc.getName(), acc.getSex(), acc.getAge()).get());
        }
    }
}

4.3.Grpc服务端启动引导类代码

不多说啥了,代码里有注释,所以上代码!

rpc引导类BootStrap.java代码如下:

public class BootStrap {

    private Server server;

    /**
     * 服务启动类
     *
     * @param port 端口
     * @throws IOException
     */
    private void start(int port) throws IOException {
        server = ServerBuilder.forPort(port)
                //注册服务
                .addService(new GreeterImpl())
                .addService(new AccountImpl())
                .build()
                .start();

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.err.println("*** JVM 关闭,导致gRPC服务关闭!");
                BootStrap.this.stop();
                System.err.println("*** 服务关闭");
            }
        });
    }

    /**
     * RPC 服务关闭
     */
    private void stop() {
        if (server != null) {
            server.shutdown();
        }
    }

    /**
     * 设置守护进程
     */
    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    /**
     * RPC服务启动main函数
     */
    public static void main(String[] args) throws IOException, InterruptedException {
        final BootStrap server = new BootStrap();
        server.start(50051);
        server.blockUntilShutdown();
    }
}

4.3.Grpc测试

测试类代码如下:

public class Test {
    public static void main(String[] args) throws Exception {

        BaseClient client = new BaseClient("localhost", 50051);
        try {
            System.out.println("===============GreeterClient============");
            GreeterClient greeterClient = new GreeterClient(client);
            greeterClient.greet("Hello");

            System.out.println("===============AccountClient============");
            AccountClient accountClient = new AccountClient(client);

            System.out.println("===============新增============");
            accountClient.addAccount("张飞", "男", 45);

            System.out.println("===============查找============");
            accountClient.queryAccout("测试");


        } finally {
            client.shutdown();
        }
    }

}

好了,所有的前序工作都做完了,开始测试了!

  • 启动rpc引导类BootStrap.java
  • 运行客户端测试类

服务端效果图如下:


server.png

客户端效果图如下:


client.png



打完收工,项目代码地址

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 转自:http://blog.csdn.net/kesonyk/article/details/50924489 ...
    晴天哥_王志阅读 24,551评论 2 38
  • 由于工程项目中拟采用一种简便高效的数据交换格式,百度了一下发现除了采用 xml、JSON 还有 ProtoBuf(...
    黄海佳阅读 48,032评论 1 23
  • "今天拿不到钱,只能等到五六天,下周才能。"愤愤不平,他脸上的表情充斥着愤怒与急不可耐。中国人的年关,对于他来说是...
    王曼乔阅读 268评论 0 0
  • 到了大学之后,觉得越来越累,真正开心的笑容越来越少,越来越想一个人独自呆着,不管干什么都好。我理解地域差异,不过我...
    晴枝阅读 148评论 0 1