Protobuf用过没?

一个故事

这也是很久之前了,在一直都怀念的读书时代,参与的第一个的项目,其中有一部分网络通信,基于socket编程。网络通讯TCP/IP相当于交通工具,上层应用协议还得自己设计。学过计算机网络这门课的,自然会对所学的知识举一反三。

首先查看一个TCP的协议格式, 采用二进制的表示方式进行数据表示。比如一个端口unsigned short,那么网络传输就是16bits。那么相对于二进制传输的协议,比如一些JSON直接是字符串形式传输的,那么一个端口比如65530,那么在JSON中就要用5个字节去分别表示这个5个字节6,5,5,3,0, 会占用更多的带宽。

那么假设我们需求是传递一个学生信息(这个信息也将作为本文后续的例子),信息描述如下:

//这里故意用的汉语拼音,防止英文单词多重含义
enum XingBie
{
  FEMALE,
  MALE
};

struct School
{
  std::string m_strName; //学校名字
  std::string m_Address; //学校地址
};

struct Student
{
  std::string   m_strName;   //姓名
  unsigned int m_uAge;     //年龄
  XingBie      m_eXingbie; //性别
  std::vector<School> m_vSchools; //学习过的学校
};

先来说说通信协议的定义:

  • 整形: 就采用四个字节
  • 字符串: 方法有多种,假设选择了最后一种。
    • 可以在协议中以\0结尾表示结束,也可以在字符串
    • 以固定长度来表示,比如255
    • 在字符串的表述前面加一个长度,这样也可以用来表示任意长度的,任意字符的字节流
  • 数组: 比如上述的Student就读过多所School,那么可以在数组前面加个数量,然后依次输入School信息

这个是一个刚入行的程序员设计的,结果如下.

接下来就会涉及到一个问题了,那就是序列化反序列化

  • 序列化: 内存里面的对象是连续内存的,但是对象管理啊的数据不一定,序列化就是将这些内存的数据表示到连续的内存中。作为客户端,将序列化的内容发送到服务端。
  • 反序列化: 一般来说接受到数据的服务器再将数据反序列化为内存里对象的结构状态,便于我们去操作。

而这些序列化的方法就由上述定义的协议来进行代码编写,反序列化则是一个解析数据的过程,也需要进行代码编写。

写着写着,我们就碰到了一些困难:

  • 代码后续要增加新的类型,得重新在协议中定义
  • 后续传输的数据进行变更,对象的成员和方法序列化反序列化代码都得跟着去修改,并且可能存在服务器与客户端不一致的兼容性问题。

后来有一天有个爱钻研技术的同学和我说, “你知道google出了个Protobuf吗?”,于是看了看,这个完美的解决了我们的痛点啊。那么接下来就让我们看一看本文的主人公Protocol Buffers(Protobuf)。

Protobuf for C++

Protobuf 可以快速的帮你完成以下两件事儿:

  1. 编写一个Student.proto文件,去定义你的一个Message
  2. 然后根据Student.proto, 用protoc生成相应的语言代码, 比如C++, Golang, Python, C#, Java等等。这样也便于在分布式环境中,多个不同语言的服务之间通过Protobuf去通信。其实除了分布式的网络访问方式,有时候也可以在同一个进程里跨语言调用,比如C#/Python/Golang调用C++的代码,使用了Protobuf也就不用过于关心不同语言之间数据类型兼容的问题,调用的时候只需要传入一个序列化的数据地址和数据大小。

Student.proto

这个文件用来定义我们的数据结构,将上一章的例子使用Protobuf来定义。可以看到如下:

  • 协议采用的是proto3
  • package ProtoSample 那么就转换为C++的namespace ProtoSample
  • 所有的字段均是singular, 也就是proto2中的optional, 并且注意proto3中也没有required字段了。
syntax = "proto3";

package ProtoSample;

message School {
  string name = 1;
  string address = 2;
}

enum XingBie {
  FEMALE = 0;
  MALE = 1;
}

message Student {
  string name = 1;
  int32 age = 2;
  XingBie xingbie = 3;
  repeated School schools = 4;
}

编译Student.proto

Protobuf使用的流程如下图所示:

  1. 先编译Protobuf的源码,参照官方文档即可。以Windows为例(Linux类似),编译后产生protoc.exelibprotobuf.lib
  2. protoc.exe用于编译Student.proto,将产生两个源码文件Student.pb.hStudent.pb.cc: 这个文件主要就是传输的数据结构的定义,包括设置/获取接口,序列化与反序列化等。
  3. 最后就是自己的项目编译,把上面产生的Student.pb.h, Student.pb.cclibprotobuf.lib都引用在项目中。

简单说下编译Student.proto到C++的源码文件的命令:protoc -I=. --cpp_out=. Student.proto

protobuf的代码使用

我写了个简单示例, 这个示例展示了Protobuf产生的对象的使用:

  • CreateStudent中直接构造一个对象
  • SerializeToString序列化
  • ParseFromString反序列化
  • 在有些系统构成中,可能还需要用到json,也可以直接使用MessageToJsonString将对象序列化为一个json
#include <iostream>
#include <string>
#include <vector>
#include "Student.pb.h"
#include "google/protobuf/util/json_util.h"

// 构造一个学生对象
void CreateStudent(ProtoSample::Student& student)
{
  student.set_name(u8"一个程序员的修炼之路");
  student.set_age(18);
  student.set_xingbie(ProtoSample::XingBie::MALE);
  auto pSchool = student.add_schools();
  pSchool->set_name(u8"小学");
  pSchool->set_address(u8"老地方");

  pSchool = student.add_schools();
  pSchool->set_name(u8"初中");
  pSchool->set_address(u8"新地方");
}

//打印一个学生信息
void PrintStudent(const ProtoSample::Student& student)
{
  std::cout << "Student Name: " << student.name() << std::endl
    << "Student Age: " << student.age() << std::endl
    << "Student Xingbie: " << student.xingbie() << std::endl;

  for (auto& school : student.schools())
  {
    std::cout << "School Name: " << school.name() << std::endl
      << "School Address: " << school.address() << std::endl;
  }
}

int main()
{
  setlocale(LC_CTYPE, "zh-cn.utf-8");

  // 1. 构造一个学生信息
  ProtoSample::Student student;
  CreateStudent(student);

  // 2. 序列化学生信息
  std::string strStudentInBytes;
  student.SerializeToString(&strStudentInBytes);

  // 3. 反序列化学生
  ProtoSample::Student studentNew;
  studentNew.ParseFromString(strStudentInBytes);
  PrintStudent(studentNew);

  // 4. 序列化到Json格式
  std::string strJson;
  google::protobuf::util::MessageToJsonString(studentNew, &strJson);
  std::cout << strJson << std::endl;
  
  return 0;
}

proto 2 和 proto 3

proto 1是google从2001年就开始开发内部使用,不过还不够完善,也并没有开源,后来完善后开源了proto 2

proto 2proto 3并不是相互兼容的,个人认为proto 3除了支持更多的功能,也更加简明。比如Proto 3废弃了optional, 虽然现在等同于默认的singular,但是在proto2optional int32 name可以使用has_name()来判断是否具有设置这个值,而在proto3中不可以,并且为默认值0,这个在参考3中有比较详细的讨论。

关于更多的区别可以直接查看 Proto V3:

https://github.com/protocolbuffers/protobuf/releases/tag/v3.0.0

这里我们关心一个问题,如果是一个新的项目,该使用Proto 2还是Prot 3。以下是google的官方回答, 一句话建议使用Proto 3

proto3 is the current version of the language. This is the most commonly used version of the language. We encourage new code to use proto3. proto2 is an older version of the language. Despite being superseded by proto3, proto2 is still fully supported.

Protobuf VS Json

Json也是一个广泛应用于数据传输,那么什么时候用Json什么时候选择Protobuf呢,那就要从他们的特点对比来看一看了。

使用复杂度

相对于而言JSON的使用比较方便:

  • Protobuf需要定义一个Schema文件.proto,并且需要编译,引入源码文件和库。
  • JSON直接文本形式表述,很多语言内置支持。

数据表达能力

JSON适合用于表达相对简单的数据结构,而Protobuf直接生成相应语言对应的结构,基本可以表达任意结构,更胜一筹。

数据格式

这个就看使用场景,文本的优势在于可读性好,这样更利于在一些Web调用方面更加合适,便于使用浏览器直接调试。而Protobuf适用于分布式环境中的内部交互,并且一般要求数据表达能力更强,或者使用效率更高的场景。 当然了

  • JSON采用文本, 一般来说体积比二进制大,传输的带宽和效率也会相对较低。
  • Protbuf二进制

效率

序列化反序列化,一般来说Protobuf效率更高。举个最简单的例子,比如二进制存储(Bytes),在JSON中必然要使用对字节的编码,并且解码,而在Protobuf中直接使用二进制存储。

语言支持

这个必然是Json使用的更加广泛,并且基本语言要么内置JSON解析器,要么就是有很多的SDK。而Protobuf支持的语言数量还是有限。

综合来看,个人的使用意向是,如果Json能够完整表达数据,并且没有太高的效率要求,首选JSON。否则,就选Protobuf吧。

参考

  1. 《Protocol Buffers》: https://developers.google.com/protocol-buffers/docs/overview
  2. 《Difference Between Protobuf vs JSON》: https://www.educba.com/protobuf-vs-json/
  3. 《区分 Protobuf 中缺失值和默认值》 : https://zhuanlan.zhihu.com/p/46603988

本文分享自微信公众号 - 一个程序员的修炼之路(CoderStudyShare),作者:河边一枝柳

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-08-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • protobuf在嵌入式linux下的移植及c语言调用

    关于什么是protobuf,网上搜搜一大堆,很多人用的都还是json,以为json是多种语言传输数据是万能的,看完了protobuf的实现,就明白了简单高效才是...

    杨永贞
  • Caffe: Could not find PROTOBUF Compiler(Profobuf 3.0 above)

    在用cmake生成Caffe工程文件的时候,如果你使用Protobuf 3.0以上的版本,cmake可能会产生如下的报错: CMake Error at cm...

    用户1148648
  • WCF服务上应用protobuf

    protobuf是google提供的一个开源序列化框架,类似于XML,JSON这样的数据表示语言,其最大的特点是基于二进制,因此比传统的XML表示高效短小得多。...

    张善友
  • golang-protobuf使用

    Protocol buffers是一个灵活的、高效的、自动化的用于对结构化数据进行序列化的协议,与XML、json相比,Protocol buffers序列化后...

    潇洒哥和黑大帅
  • golang使用protobuf

    为什么要使用protobuf 最近的项目中,一直使用Json做数据传输。Json用起来的确很方便。但相对于protobuf数据量更大些。做一个移动端应用,为用户...

    李海彬
  • protobuf的使用

    先上官方使用文档 https://developers.google.cn/protocol-buffers/docs/proto3

    码缘
  • 金蝶随手记团队分享:还在用JSON? Protobuf让数据传输更省更快(实战篇)

    本文接上篇《金蝶随手记团队分享:还在用JSON? Protobuf让数据传输更省更快(原理篇)》,以iOS端的Objective-C代码为例,向您演示如何使用P...

    JackJiang
  • Charles解析protobuf之初探

    在程序开发以及网络通信传输过程中最常见的数据格式就是JSON、XML,或者是一种压缩效率更高的数据格式——Google的ProtoBuf。ProtoBuf在传输...

    用户5521279
  • 在Egret项目中使用protobuf

    ProtocolBuffer是用于结构化数据串行化的灵活、高效、自动的方法,有如XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器...

    用户1428723
  • mac 上安装Protobuffer

    Protocol Buffers (ProtocolBuffer/ protobuf )是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序...

    solate
  • 使用grpc C++功能

    https://www.tianmaying.com/tutorial/pkgconfig

    杉枫
  • python调用动态链接库传送protobuf

       protobuf是Google提供的一个开源序列化框架,类似于XML,JSON这样的数据表示语言,其最大的特点是基于二进制,因此比传统的XML表示高效短小...

    py3study
  • 基于protobuf的代码生成

    前段时间我用 Python 和 Mako 模板引擎重新梳理了我们项目中的一些重复的流程。重构了所有的RPC系统。这个工作其实完成了挺久了,但是迫于懒一直拖着没写...

    owent
  • Springboot 2.0 +protobuf + Netty 实战(附源码)

    这一篇文章主要介绍如何用Springboot 整合 Netty,这里也是在网上搜寻了一些Netty例子学习后总结来的,借鉴了他人的写法和经验。如有重复部分,还请...

    java进阶架构师
  • CentOS6.5下python版本的protobuf编译及安装

    版权声明:本文为博主原创文章,转载请注明源地址。 https://blog.csdn.net...

    用户1148648
  • manjaro安装数据库失败的解决方案

    文件下载:protobuf-3.6.1.3-1-x86_64.pkg.tar.xz

    雨落凋殇
  • protobuf-net的动态Message实现

    这本来是个早就可以写的分享。因为代码几周前就迁移并准备好了。而且这也是之前项目的工具,因为可以抽离出来通用化所以单独整理出来。

    owent
  • Google Protocol Buffers三两事【知识笔记】

    小结:根据上图测评,序列化后的空间开销与解析性能上,Avro与Protobuf不相上下独占鳌头;另外根据“Protobuf协议介绍及性能实测”文中测评来看,报文...

    瓜农老梁
  • 使用protostuff自定义编解码器优化springcloud-feign性能

    Spring Cloud feign是伪RPC方式解决微服务间的调用。翻看FeignCloudFeign源码,可以看到Feign默认使用HttpUrlConne...

    生活创客

扫码关注云+社区

领取腾讯云代金券