Grpc protoc的简单使用

Grpc protoc的简单使用

Grpc:3.6.1  protoc:3.6.1  centos7.4

 

通过本篇文档可以了解protocol buffer内部的编解码机制,学习到如何源码编译安装,学习如何在一个.proto文件内定义服务,如何跟.proto文件使用protocol buffer的编译器生成客户端和服务端代码,学习如何使用grpc的c++接口为服务实现一个简单的客户端和服务器。

 

一. Protocol buffer协议介绍:

Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,用于 RPC 系统和持续数据存储系统。

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python、ruby、php、go等多种语言的 API。

 

Message格式介绍:

在.proto文件定义消息,message是.proto文件最小的逻辑单元,由一系列name-value键值对构成。

消息由至少一个字段组合而成,类似于C语言中的结构。每个字段都有一定的格式
字段格式:限定修饰符① | 数据类型② | 字段名称③ | = | 字段编码值④ | [字段默认值⑤]

限定修饰符包含:required optional repeated

数据类型包含:bool、int32、float、string、bytes、enum、message等

字段名称:建议字段的命名采用以下划线分割的驼峰式。例如 first_name 而不是firstName.

字段编码值:有了该值,通信双方才能互相识别对方的字段。编码值的取值范围为 1~2^32(4294967296),1900~2000编码值为Google protobuf 系统内部保留值。

字段默认值:是可选字段,代表该字段的默认值

 

Pb的编码实例:

现在举一个例子说明protocol buffer的传输机制,先看一个最简单的messgae定义:

message test{

  required int32 a=1 [default = 150];

}

将该对象序列化到二进制文件中,可以看到文本中的数据是:

08 96 01

 

在理解Protocol Buffer的编码规则之前,首先需要了解varints: varints是一种使用一个或多个字节表示整型数据的方法。其中数值本身越小,其所占用的字节数越少。

Protocol buffer对整数的编码优化:
      在varint中,除了最后一个字节之外的每个字节中都包含一个msb(most significant bit)设置(使用最高位),这意味着其后的字节是否和当前字节一起来表示同一个整型数值。而字节中的其余七位将用于存储数据本身。由此我们可以简单的解释一下Base 128,通常而言,整数数值都是由字节表示,其中每个字节为8位,即Base 256。然而在Protocol Buffer的编码中,最高位成为了msb,只有后面的7位存储实际的数据,因此我们称其为Base 128(2的7次方)。

 

首先先看96 01 ,二进制是1001 0110 0000 0001根据varint的规则:

  1001 0110 0000 0001

à  001 0110 0000 0001 #去掉msb位,此时msb是1,代表下一个字节和当前字节一起表示某个整数

à  0000 0001 001 0110  #字节序转换

à  1001 0110

à  128 + 16 + 4 + 2 = 150

 

再来看08的解码过程,在进行消息编码时,key/value被连接成字节流,value的值是150已经解码完成,08就是key的值,key的值根据protocol定义是由字段编码值和字段数据类型合成编码所得,公式如下:

Key = field_number << 3 | field_type

Protocol Buffer可以支持的字段类型:

 

定义的字段数据类型是int32,属于varint,对应的type是0,定义的字段编码值是1,所以:

08 = 1 << 3 | 0

 

Protocol buffer对字符串的编码优化:

但是如果Value是一个很长的字符串,每个字节都拿出1个比特来区分边界就太浪费空间了,而且字符串本身就是一个一个字节的,被打乱后也会影响解码效率。因此,PB将Value长度信息的指示可以放在Key和Value之间。(长度本身也是一个整数,就用前面那种方法进行编码即可),在解码Value时,解析长度就可以知道Value值到哪里结束。

 

这样就完成了对test的编码和解码,该部分旨在让人简单理解protocol buffer内部的机制。

 

二. Grpc介绍:

    gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。

在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法

Grpc使用http/2的传输协议并且能支持很多的语言(C, C++, Python, PHP, Nodejs, C#, Objective-C、Golang、Java)

gRPC已经应用在Google的云服务和对外提供的API中,其主要应用场景如下: 
- 低延迟、高扩展性、分布式的系统 
- 同云服务器进行通信的移动应用客户端 
- 设计语言独立、高效、精确的新协议 
- 便于各方面扩展的分层设计,如认证、负载均衡、日志记录、监控等

gRPC默认的序列化方式是protobuf.

三. grpc和protoc的安装:

   1. 安装grpc的相关依赖,执行以下命令:

    

   2. 下载grpc源码:

      

      进入到grpc目录下,执行:

      

      该命令主要是下载grpc第三方的软件包,下载后会放在third_party对应的目录下

   3. 首先编译protoc,进入到third_party/protobuf目录下:

      

   4. 编译grpc,切换到grpc源码目录下,执行:

      

   至此,grpc和protoc已经安装完成。

 

四. 代码实例解析:

Message定义

1. 该实例代码主要实现了hello world得回显功能,首先定义example.proto文件,文件内容如下:

第一行指定了使用proto3语法,如果没有指定,编译器会使用proto2。这个指定语法行必须是文件的非空非注释的第一个行。

SearchRequest和SearchResponse定义了protocol buffer的message,里面定义了客户端与服务器端相互传输的数据类型以及与protocol buffer协议相关的信息。SearchRequest定义了客户端需要发送的信息,SearchResponse定义了服务器端返回的信息。

如果想要将消息类型用在GRPC系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。对应到实例中,定义了一个SearchService的服务并定义了一个Search的rpc调用方法,该方法接收SearchRequest并返回SearchResponse。

grpc允许定义4种类型的 service 方法:简单rpc,服务器端流式rpc,客户端流式rpc,双向流式rpc,本实例中Search方法只是一个简单的rpc,不涉及到流式操作。

 

自动生成客户端和服务端代码

接下来我们需要从 .proto 的服务定义中生成 gRPC 客户端和服务器端的接口。我们通过 protocol buffer 的编译器 protoc 以及一个特殊的 gRPC C++ 插件来完成。

命令如下:

# protoc --cpp_out=./ example.proto  

该命令会生成example.pb.h 和 example.pb.cc两个文件,主要是有关消息类的声明和实现

#protoc --grpc_out=./ --plugin=protoc-gengrpc=/usr/local/bin/grpc_cpp_plugin example.proto  

该命令会生成example.grpc.pb.h 和 example.grpc.pb.cc两个文件,主要是服务类相关的声明和实现,在.proto文件里面定义的service会自动生成一个同名的c++类,该类会嵌套一个Stub的类,Stub类中会有对应service下的rpc接口的实现,该实例中对应的rpc接口是search,并且service同名类中 有一个NewStub的接口,该接口会调用Stub类并返回一个Stub的对象即存根。

 

服务端和客户端的创建

上面是完整的客户端代码,主要实现了ExampleClient类,在该类的构建函数中会调用SearchService的NewStub接口,该接口会返回一个存根,我们使用存根来调用服务器端的方法,首先需要为存根创建一个grpc的channel,channel用来指定想要连接的服务器地址和端口号,本实例中连接的是本的50051端口。

并在ExampleClient类中封装了Search接口,Search会通过存根stub调用异步接口AsyncSearch调用服务端接口,并调用server端的Search API,传送的参数是字符串’world’,当server端收到该请求后会组装成’hello world’返回给client

    该截图是server端完整代码,该代码会构建并启动一个grpc的服务,通过使用ServerBuilder去构建和启动服务器,步骤如下:

1)创建我们的服务实现类 SearchRequestImpl 的一个实例,该类继承SearchService::Service类并重构了Search接口,接口的功能就是拼接’hello’和客户端发来的信息。

2)创建工厂类 ServerBuilder 的一个实例。

3)在生成器的 AddListeningPort() 方法中指定客户端请求时监听的地址和端口。

4)用生成器注册我们的服务实现。

5)调用生成器的 BuildAndStart() 方法为我们的服务创建和启动一个RPC服务器。

6)调用服务器的 Wait() 方法实现阻塞等待,直到进程被杀死或者 Shutdown() 被调用

 

代码地址:https://github.com/zhaohb/grpc_demo.git

    • 0
      点赞
    • 11
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值