gRPC快速入门:01——Protocol Buffers

如果你要给 .Net Core 寻找一个通信框架,或 寻找一个多平台多语言的通信框架,gRPC 是不错的选择。
gRPC 支持 C++、C#、Dart、Go、Java、Node、php、python、ruby、objective-C、WebJS,跨平台,跨语言。

gRPC (g = google, RPC = Remote Procedure Call [远程过程调用])

本教程简介

本教程使用 .Net Core 和 Visual Studio 开发工具

本教程目的:

  1. 学习 Protocol Buffers
  2. 学习 gRPC
  3. 使用 gRPC 实现一个简单的聊天室

为什么学习 gRPC 之前要先学习 Protocol Buffers,因为:

  • gRPC 的数据序列的方式是使用 Protocol Buffers。
  • gRPC 描述通信协议的方式也是使用 .proto文件。

Protocol Buffers 简介

Protocol Buffers 是 google 的一个开源项目,它是一种灵活、高效、自动化,用于序列化结构化数据,如 XML(json),但更小(smaller),更快(faster),更简单(simpler)。

和 XML(json) 比,Protocol Buffers

  • 更小:比 XML 小 3 到 10 倍
  • 更快:比 XML 快 20 到 100 倍
  • 更简单:序列化过程比 XML 简单(底层简单,编码复杂了)

优点的原因在于:

  • Protocol Buffers 序列化采用 Varint 编码
    Varint 一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。
    这能减少用来表示数字的字节数。

  • 封解包的速度
    XML 封解包过程需要从文件中读取出字符串,再转换为 XML 文档对象结构模型。之后,再从 XML 文档对象结构模型中读取指定节点的字符串,最后再将这个字符串转换成指定类型的变量。这个过程非常复杂,其中将 XML 文件转换为文档对象结构模型的过程通常需要完成词法文法分析等大量消耗 CPU 的复杂计算。
    反观 Protocol Buffers,它只需要简单地将一个二进制序列,按照指定的格式读取到 编程语言 对应的结构类型中就可以了。

因此对应的缺点是:Protocol Buffers 序列化成二进制后,可读性差。

所以:
如果是做 前后端分离的通信数据序列,还是选择json吧,
如果是做 服务与服务之间的通信数据序列,可以考虑选择 Protocol Buffers。

定义序列化格式

回想一下为了将数据序列化成 XML(json),我们一般会先根据来编写对应的类(数据结构),
但各种语言定义数据结构的语法是不一样的,而使用 Protocol Buffers 是将想序列化的数据结构定义在一个 .proto文件中,
然后使用 Protocol Buffers 编译器 将其生成各语言对应的类(数据结构),还包含了序列化和反序列化方法。

接下来我们来看看官方教材中 addressbook.proto 学习一下是如何在 .proto文件中定义数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

syntax = "proto3"; // 声明语法规则,之前还有 proto2
package tutorial; // 申明了一个包,这样有助于避免在不同项目间出现命名空间冲突。

// 引入其它包,留意下文的 last_updated 数据类型
//import "google/protobuf/timestamp.proto";

// 可选,指定生成 C#类时的命名空间,可以不指定,你生成的类会被放置到和包名称一至的命名空间中
//option csharp_namespace = "Google.Protobuf.Examples.AddressBook";

// 可选,指定生成 Java类时的包名与类名
//option java_package = "com.example.tutorial";
//option java_outer_classname = "AddressBookProtos";

message Person { // 会生成一个对应的 Class Person 类
string name = 1; // 会生成 Class Person 类字段 name_ 和 属性 Name
int32 id = 2; // 注意!这里的 1、2、3 不是字段的值,是标记
string email = 3;

enum PhoneType { // 定义枚举
MOBILE = 0;
HOME = 1;
WORK = 2;
}

message PhoneNumber { // 嵌套类
string number = 1;
PhoneType type = 2;
}

// 相当于 List<PhoneNumber> Phones {get;}
repeated PhoneNumber phones = 4;

// 其他包引入进来的数据类型
//google.protobuf.Timestamp last_updated = 5;
}


message AddressBook { // 生成一个 AddressBook 类
repeated Person people = 1; // 相当于 List<Person> People {get;}
}

每一个元素都有 = 1, = 2这样的标记,这些标记是字段在二进制编码内的唯一标识标签,
标签数字 1-15 比更大的数字需要更少的字节编码,做为优化你可以为常用的或 repeated 元素使用这些标签数字,
把 16 以上的标签留给不经常使用的元素。
重复字段的每一个元素都需要重新编码标签数字,所以重复字段特别适合这种优化。

更多语法规则,请浏览 官方文档 (可能需要梯子),或者搜索:Protobuf 语言指南

编译 Protocol Buffers

现在我们已经有了一个 .proto文件,接下来我们需要做的是生成相关的类,
这些类用来序列化和反序列化 AddressBook 消息(包含 Person和 PhoneNumber)。
为了能完成这样的工作,你需要运行 Protocol Buffers 的编译器,编译你的 .proto文件。

Protocol Buffers 编译器安装

官方下载地址

选择 protoc-x.x.x-win32.zip x.x.x表示版本,
另外该下载页面,还包含了不同的语言的 gRPC 实现和 Demo,有兴趣的可以下载之。

解压zip文件,可以看到 bin 文件夹中有一个 protoc.exe 文件,
现在将 bin 文件夹改名成 protoc 文件夹,将 protoc 文件夹复制到C盘。

接下来,我们需要添加 环境变量

  1. 右击 我的电脑 (win10是 此电脑)
  2. 属性
  3. 高级系统设置
  4. 环境变量
  5. 双击 path
  6. 点击 浏览 然后选择C盘下的 protoc 文件夹
  7. 确定

打开 cmd,输入 protoc --version 如有输出版本,则说明安装成功。

接下来,看看怎么把 addressbook.proto 编译生成我们需要的 C# 代码。

1
protoc --csharp_out=$DST_DIR $SRC_DIR/addressbook.proto

--csharp_out : 是指,将其生成 C# 代码文件。
$DST_DIR :是指生成到哪个目标文件夹,需要替换成具体路径。
$SRC_DIR :源文件夹,需要替换成具体路径。

执行命令前,先将 google.protobuf.Timestamp last_updated = 5; 注释掉,因为我们没有import对应的包。
执行命令后,应该会在目标文件夹中生成一个 Addressbook.cs 文件。

那么问题就来了,如果 .proto 文件多,而且每次修改都要输入命令生成,是多么蛋疼的事情,
好在我们有宇宙最强大的 IDE Visual Studio,而且 Google 也为 VS 写了相关的工具。

结合 Visual Studio 自动化编译

因为我们的目标是使用 gRPC 实现一个简单的聊天室,所以先创建整个解决方案吧。

1
2
3
4
5
6
7
8
9
Chat.sln
├── Chat.Client.csproj
| └── Program.cs
|
├── Chat.Protocol.csproj
| └── addressbook.proto
|
└── Chat.Server.csproj
└── Program.cs

首先在 Chat.Protocol.csproj 项目下,创建一个 addressbook.proto 文件,该文件的内容依然和上面的一样。
但该文件没有语法高亮,也没有智能提示,
所以点击 VS 菜单中的 工具扩展与更新联机 → 搜索 Protobuf
安装 Protobuf Language Service
重启 VS 后,proto文件就有语法高亮和智能提示了。
(该插件最后更新是2017年,VS Code上也有相关的插件,比较新,提示得比较好)

Protobuf Language Service 只是语法高亮和智能提示,并不会将 proto 文件内容生成相应的代码。

右击 解决方案'Chat'管理解决方案的 NuGet 程序包(N)...
Chat.Protocol 项目,添加

  • Google.Protobuf 这个提供序列化和反序列化的一些扩展方法。
  • Grpc.Tools 这个提供将 proto 文件生成相应代码,并不是添加了就行,还需要相关设置。
  • Grpc 这个是 Grpc 的实现。

Chat.Server 引用 Chat.Protocol
然后在 Chat.Server 中的 Program.cs 中敲 Tutorial 并没有相关的提示,
即使你右击 Chat.Protocol生成(U)也不会有。

现在我们需要右击 addressbook.proto属性(R)生成操作 选择 Protobuf compiler
这个时候再右击 Chat.Protocol生成(U),那么在 Chat.Server 中则有相应的 Tutorial 命名空间和 AddressBook 类了。

那么生成的代码到底在哪里呢?请查看 Chat.Protocolobj 文件夹下的文件,你会有所发现…

序列化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;

using Newtonsoft.Json;
using Google.Protobuf;
using Tutorial;

namespace Chat.Server
{
class Program
{
static void Main(string[] args)
{
var pXiaoBai = new Person();
pXiaoBai.Name = "小白";
pXiaoBai.Phones.Add(new Person.Types.PhoneNumber()
{
Type = Person.Types.PhoneType.Mobile,
Number = "10086"
});

// 序列化
var bytes = pXiaoBai.ToByteArray();

//比较序列化后的长度
Console.WriteLine(JsonConvert.SerializeObject(pXiaoBai).Length);//长度是:70
Console.WriteLine(pXiaoBai.ToByteString().Length);//长度是:17


// 反序列化
var p = Person.Parser.ParseFrom(bytes);

Console.WriteLine(p.Name);
Console.WriteLine(p.Phones[0].Number);

Console.ReadLine();
}
}
}

结束语

本章讲解了 Protocol Buffers 是什么,以及各种相关工具的应用,
下一章将讲解 gRPC 的 Hello World 例子,开始进入 gRPC 的大门。

觉得文章对您有帮助,请我喝瓶肥宅快乐水可好 (๑•̀ㅂ•́)و✧
  • 本文作者: 阿彬~
  • 本文链接: https://iweixubin.github.io/posts/grpc/quickstart-protocol-buffers/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 免责声明:本媒体部分图片,版权归原作者所有。因条件限制,无法找到来源和作者未进行标注。
         如果侵犯到您的权益,请与我联系删除