RPC(Remote Procedure Call,远程过程调用)框架是分布式服务的基石,实现RPC框架需要考虑方方面面。其对业务隐藏了底层通信过程(TCP/UDP、打包/解包、序列化/反序列化),使上层专注于功能实现;框架层面,提供各类可选架构(多进程/多线程/协程);应对设备故障(高负载/死机)、网络故障(拥塞/网络分化),提供相应容灾措施。

RPC节点间为了协同工作、实现信息交换,需要协商一定的规则和约定,例如字节序、压缩或加密算法、各字段类型。通信协议的应用随处可见,例如我们对可选信息或字段经常使用TLV进行编码,HTTP、FTP等协议基于可读文本的 "Field: Value" 格式,各种系统也经常使用json、XML格式完成相互间通信。

不同的通信协议适用于不同的应用场景,比如内部系统的交互我们选择json,一来可读性较好,二来各种语言都提供了解析json的库、方便编码。Google Protocol Buffers是生成环境中常用的通信协议,除了可以设定Client/Server间通信格式,Protocol Buffers还对数据进行压缩,节省传输流量、加快传输速度。下面我们来了解Google Protocol Buffers。

Protocol Buffers

我们看如何使用Protocol Buffers(以下简称PB),首先在.proto文件中定义数据格式,下面以Person.proto为例:

message Person {
required string name = ;
required int32 id = ;
optional string email = ; enum PhoneType {
MOBILE = ;
HOME = ;
WORK = ;
} message PhoneNumber {
required string number = ;
optional PhoneType type = [default = HOME];
} repeated PhoneNumber phone = ;
}

message类型内,可以定义int、string、bool、string等类型的字段,也可以嵌套定义messages类型。每个字段可以是required、optional或repeated类型,分别表示必须每次通信必须填充该字段、可选或可重复。每个message类型内的每个字段被赋值唯一的数字值,PB以二进制格式进行数据传输,数字值在二进制中作为该字段的标识。关于PB数据格式的更多内容可参考Protocol Buffers Language Guide

完成数据定义后,接下来可以使用protoc工具解析Person.proto文件,生成Person类:

protoc --cpp_out=/home/bangerlee/PB ./Person.proto

执行以上命令后,可以看到 /home/bangerlee/PB 目录下生成了两个文件:

person.pb.cc  person.pb.h

其中定义了操作(get/set)Person类各个字段的函数。

有了接口,我们就可以在代码中这样使用Person类,写入操作如下:

Person person;
person.set_name("bangerlee");
person.set_id();
person.set_email("bangerlee@gmail.com");
fstream output("myfile", ios::out | ios::binary);
person.SerializeToOstream(&output);

读取操作如下:

fstream input("myfile", ios::in | ios::binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;

以上我们初步了解了如何使用PB,PB运用了一些编码规则,使得需要传输的数据(二进制格式)更小,下面我们就来了解PB如何对不同数据类型的编码规则。

编码(Encoding)

对整形int、字符串类型string等,PB有不同的编码方式。对整型int,PB使用了Varints编码方式,Varints编码的优势是使用了更少的bytes来表示很小的int类型值。

Varints编码方式中,每个byte的最高位bit有特殊含义,如果为1,表示后续的byte也是这个数字的一部分;如果为0,则表示结束。剩余的7个bit用于表示数据。数字300用Varints编码方式表示为:

   

由Varints编码规则,去掉第一个byte的最高位1,去掉第二个byte的最高位0,则有:

Varints字节序使用little-endian,以上数字用big-endian并转换成10进制有:

→    ++

→ + + + =

以上了解了Varints对int整型的编码方式,我们再来看PB如何编码更多数据类型:

PB编码中,数据以key-value的形式表示,第一个byte即为key。以上表格中不同数据类型对应指定type值,假设message中各字段的数字标识为tag,则key、type和tag有以下对应关系:

key = tag <<  | type

即key的最后3个bit用于存储type,有了这层关系,我们试着演算PB中对int和string的编码。

假设我们截获到以下PB数据:

  

这段数据具体表示什么?我们用以上对应关系演算一下,首先该数据key是08,二进制表示即:

 

最后3个bit表示type,即type为0(Varint格式数据),左移3位得到tag值为1。有了这些信息,我们可以知道这个数据应该是这样定义的:

message Test1 {
xxx int32 a = ;
}

继续地,我们用Varint格式来解析 96 01,有以下演算过程:

  =
→ ++ (丢弃最高位的bit并转为big-endian)

→ + + + =

因此我们可以知道这段数据表示150这个数。

又假设我们截获到以下一段PB编码:

       6e 

同样套用以上关系,key是12,二进制表示即:

 

最后3个bit表示type,即type为2(Length-delimited),左移3位得到tag值为2。有了这些信息,我们知道这个数据可能是这样定义的:

message Test2 {
xxx string b = ;
}

数据类型具体是string、bytes或其他,这并不影响我们解析这段数据,对于Length-delimited格式数据,第2个byte表示数据长度(Len),对应以上编码即Len为7,这实质是TLV编码格式。

后续的7个bytes表示有效的传输数据,为UTF-8编码下的"testing"字符串。

小结

以上介绍了通信协议 - Google Protocol Buffers,了解了其基本使用方法和编码方式。PB支持前向兼容,可以在不修改Client/Server程序的情况下修改其中一端的数据格式,在各种RPC框架中经常可以看到它的身影。

Reference: Protocol Buffers Developer Guide

Protocol Buffers Encoding

RPC框架实现 - 通信协议篇的更多相关文章

  1. RPC框架实现

    转载RPC框架实现 RPC(Remote Procedure Call,远程过程调用)框架是分布式服务的基石,实现RPC框架需要考虑方方面面.其对业务隐藏了底层通信过程(TCP/UDP.打包/解包.序 ...

  2. 基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇

    基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇 前提 最近对网络编程方面比较有兴趣,在微服务实践上也用到了相对主流的RPC框架如Spring Cloud Gateway底层也切换 ...

  3. 一个简单RPC框架是怎样炼成的(I)——开局篇

    开场白,这是一个关于RPC的相关概念的普及篇系列,主要是通过一步步的调整,提炼出一个相对完整的RPC框架. RPC(Remote Procedure Call Protocol)--远程过程调用协议, ...

  4. 基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇

    前提 前置文章: Github Page:<基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> Coding Page:<基于Netty和SpringBoot实现 ...

  5. 基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇

    前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> 前 ...

  6. Hadoop系列番外篇之一文搞懂Hadoop RPC框架及细节实现

    @ 目录 Hadoop RPC 框架解析 1.Hadoop RPC框架概述 1.1 RPC框架特点 1.2 Hadoop RPC框架 2.Java基础知识回顾 2.1 Java反射机制与动态代理 2. ...

  7. 看了这篇你就会手写RPC框架了

    一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...

  8. 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍

    概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...

  9. RPC框架调用过程详解

    RPC框架调用过程详解 2017年09月16日 21:14:08 荷叶清泉 阅读数 6275   版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...

随机推荐

  1. 【.net 深呼吸】细说CodeDom(8):分支与循环

    有人会问,为啥 CodeDom 不会生成 switch 语句,为啥没生成 while 语句之类.要注意,CodeDom只关心代码逻辑,而不是语法,语法是给写代码的人用的.如果用.net的“反编译”工具 ...

  2. 自定义基于 VLC 的视频播放器

    前言(蛋疼的背景故事) 前段时间,接了一个小项目,有个需求是要在系统待机一段时间以后,循环播放 MV(类似于 Windows 系统的屏幕保护). 听到这个需求,我首先想到的是 MediaPlayer ...

  3. 创建 OVS flat network - 每天5分钟玩转 OpenStack(134)

    上一节完成了 flat 的配置工作,今天创建 OVS flat network.Admin -> Networks,点击 "Create Network" 按钮. 显示创建页 ...

  4. 关于开启.NET在线提升教育培训的通知! - 可在此页面观看在线直播!

    年前在线公开课程通知: 近期在开启VIP课程,隔天讲一次,年前其它时间插空讲公开课,主题:设计模式系列 1:培训 - 大概不会讲的内容: 1:不讲系列. 2:不讲入门. 3:不讲我不懂的! 2:培训 ...

  5. 【WCF】使用“用户名/密码”验证的合理方法

    我不敢说俺的方法是最佳方案,反正这世界上很多东西都是变动的,正像老子所说的——“反(返)者,道之动”.以往看到有些文章中说,为每个客户端安装证书嫌麻烦,就直接采用把用户名和密码塞在SOAP头中发送,然 ...

  6. CENTOS 6.5 平台离线安装 Apache2.4

    一.下载Apache 2.4 http://httpd.apache.org/download.cgi http://mirrors.cnnic.cn/apache//httpd/httpd-2.4. ...

  7. 算法与数据结构(十七) 基数排序(Swift 3.0版)

    前面几篇博客我们已经陆陆续续的为大家介绍了7种排序方式,今天博客的主题依然与排序算法相关.今天这篇博客就来聊聊基数排序,基数排序算法是不稳定的排序算法,在排序数字较小的情况下,基数排序算法的效率还是比 ...

  8. Linux碎碎念

    在学习Linux过程中,有许多有用的小技巧.如果放在纸质的笔记本上,平时查阅会相当不方便.现在以一种“碎碎念”的方式,汇集整理在此,目前还不是很多,但随着学习.工作的深入,后续会陆陆续续添加更多的小技 ...

  9. 快速了解微信小程序的使用,一个根据小程序的框架开发的todos app

    微信官方已经开放微信小程序的官方文档和开发者工具.前两天都是在看相关的新闻来了解小程序该如何开发,这两天官方的文档出来之后,赶紧翻看了几眼,重点了解了一下文档中框架与组件这两个部分,然后根据简易教程, ...

  10. App解读

    一直不懂别人口中说的原生开发.混合式开发.今天突然看了一篇文章讲解的是什么叫做原生App?移动 Web App?混合APP?分享给大家. 原生App是专门针对某一类移动设备而生的,它们都是直接安装到设 ...