哪里有建网站的,仙居建设规划局网站,外贸推广网站收费吗,如何制作旅游网站目录 1. 概述 2.环境安装 2.1编译源码包 2.2下载源码并解压 3. 实例演示 3.1 书写proto文件 3.2 编译 .proto 文件 3.3 Writer.cpp代码 3.4 Reader.cpp代码 3.5 执行Writer和Reader 4. ProtoBuf的Encoding 4.1 Message Buffer 4.2 Varint 4.3 Key 4.4 Zi…目录1. 概述2.环境安装2.1编译源码包2.2下载源码并解压3. 实例演示3.1 书写proto文件3.2 编译 .proto 文件3.3 Writer.cpp代码3.4 Reader.cpp代码3.5 执行Writer和Reader4. ProtoBuf的Encoding4.1 Message Buffer4.2 Varint4.3 Key4.4 Zigzag 编码5. API介绍6. Protobuf的优点6.1 优点6.2 不足之处1. 概述在移动互联网时代手机流量、电量是最为有限的资源而移动端的即时通讯应用无疑必须得直面这两点。解决流量过大的基本方法就是使用高度压缩的通信协议而数据压缩后流量减小带来的自然结果也就是省电因为大数据量的传输必然需要更久的网络操作、数据序列化及反序列化操作这些都是电量消耗过快的根源。当前即时通讯应用中最热门的通信协议无疑就是Google的Protobuf了基于它的优秀表现微信和手机QQ这样的主流IM应用也早已在使用它。本文将详细介绍Protobuf的使用、原理等。Protocol Buffers 是一种轻便高效的结构化数据存储格式可以用于结构化数据串行化或者说序列化。xml、json也可以用来存储此类结构化数据但是使用protobuf表示的数据能更加高效并且将数据压缩得更小大约是json格式的1/10xml格式的1/20。Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。2.环境安装2.1编译源码包从githubhttps://github.com/protocolbuffers/protobuf 下载源代码此处 我下载的是匹配我们项目的版本protobuf-3.21.7。2.2下载源码并解压以ubuntu安装为例安装步骤如下tar –xzf protobuf-cpp-3.21.7.tar.gz cd protobuf-cpp-3.21.7/ ./configure --prefix$INSTALL_DIR //设置install路径 make make check make install3. 实例演示使用 Protobuf 和 C 开发一个十分简单的读写程序。这个程序由Writer和Reader组成。 Writer 负责将一些结构化的数据写入一个磁盘文件Reader 则负责从该磁盘文件中读取结构化数据并打印到屏幕上。图13.1书写proto文件首先我们需要编写一个 proto 文件定义我们程序中需要处理的结构化数据在 protobuf 的术语中结构化数据被称为 Message。proto 文件非常类似 java 或者 C 语言的数据结构体定义。图2在上例中package 名字叫做 lm定义了一个消息 Content该消息有三个成员类型为 int32 的 id另一个为类型为 string 的成员 str。opt 是一个可选的成员即消息中可以不包含该成员。3.2编译 .proto文件写好 proto 文件之后就可以用 Protobuf 编译器将该文件编译成目标语言了。本例中我们将使用 C。我们可以新建一个测试目录/app ; 把include目录下的文件都按照该目录结构和lib/libprotobuf.a复制到所测试的目录中去图3执行安装目录下bin目录中的protoc程序将写好的proto 文件用Protobuf 编译器将该文件编译成目标语言。命令生成Im.Content.pb.h和Im.Content.pb.cc文件如下图所示图43.3Writer.cpp代码现在要将数据存入磁盘该结构化数据由Im::Content类的对象表示它提供一系列的get/set函数用来修改和读取结构化数据中的数据成员。当需要将该结构化数据保存到磁盘上时类Im::Content已经提供相应的方法来把一个复杂的数据变成一个字节序列可以将这个字节写入磁盘。图5编译Writer.cpp文件图63.4Reader.cpp代码对于Reader只需从log文件中读取反序列化后就能获得结构化的数据。利用Im::Cotent对象的ParseFromIstream方法从一个fstream流中读取信息并反序列化此后ListMsg 中采用 get 方法读取消息的内部信息并进行打印输出操作。参考Writer对齐进行编译图73.5执行Writer和Reader通过Writer把数据序列化写到文件中然后通过Reader读取出来图84. ProtoBuf的Encoding4.1 Message BufferProtobuf序列化后所生成的二进制消息非常紧凑这得益于 Protobuf采用的非常巧妙的 Encoding方法首先介绍一下Message Buffer的组成图9采用这种 Key-Pair 结构无需使用分隔符来分割不同的 Field。对于可选的 Field如果消息中不存在该 field那么在最终的 Message Buffer 中就没有该 field这些特性都有助于节约消息本身的大小。4.2 VarintVarint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。比如对于 int32 类型的数字一般需要 4 个 byte 来表示。但是采用 Varint对于很小的 int32 类型的数字则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面采用 Varint 表示法大的数字则需要 5 个 byte 来表示。从统计的角度来说一般不会所有的消息中的数字都是大数因此大多数情况下采用 Varint 后可以用更少的字节数来表示数字信息。下面就详细介绍一下 Varint。Varint 中的每个 byte 的最高位 bit 有特殊的含义如果该位为 1表示后续的 byte 也是该数字的一部分如果该位为 0则结束。其他的 7 个 bit 都用来表示数字。Google Protocol Buffer 字节序采用 little-endian 的方式。message Test { optional int32 a 1; }对于这个例子来说如果给a赋值150那么最终得到的编码是什么呢150的2进制编码是1001 0110 。 由于数据只有低七位有效所以数据可以写成 000 0001 001 0110. 然后因为小端模式所以交换字节位置001 0110 000 0001 。 因为最高位表示后面byte是否是数据的一部分所以高字节最高位填写1低字节最高位填写0最终得到编码为1001 0110 0000 0001解码的话就是把这个过程反过来1001 0110 0000 0001 根据两个字节的最高位可以推断出来这里的有效数据是两个字节。 去除高字节的最高位跟低字节的最高位可以得到编码 001 0110 000 0001. 因为小端模式反一下字节序 000 0001 001 0110.4.3KeyKey用来标识具体的 field在解包的时候Protocol Buffer根据 Key就可以知道相应的 Value应该对应于消息中的哪一个 field。Key 的定义如下(field_number 3) | wire_type可以看到 Key 由两部分组成。第一部分是 field_number比如消息 lm.helloworld 中 field id 的 field_number 为 1。第二部分为 wire_type。表示 Value 的传输类型。Wire Type 可能的类型如下表所示图104.4Zigzag编码在我们的例子当中field id 所采用的数据类型为 int32因此对应的 wire type 为 0。或许会看到在 Type 0 所能表示的数据类型中有 int32 和 sint32 这两个非常类似的数据类型。Google Protocol Buffer 区别它们的主要意图也是为了减少 encoding 后的字节数。在计算机内一个负数一般会被表示为一个很大的整数因为计算机定义负数的符号位为数字的最高位。如果采用 Varint 表示一个负数那么一定需要 5 个 byte。为此 Google Protocol Buffer 定义了 sint32 这种类型采用 zigzag 编码。Zigzag 编码用无符号数来表示有符号数字正数和负数交错这就是 zigzag 这个词的含义了。使用 zigzag 编码绝对值小的数字无论正负都可以采用较少的 byte 来表示充分利用了 Varint 这种技术。其他的数据类型比如字符串等则采用类似数据库中的 varchar 的表示方法即用一个 varint 表示长度然后将其余部分紧跟在这个长度部分之后即可。通过以上对 protobuf Encoding 方法的介绍想必您也已经发现 protobuf 消息的内容小适于网络传输。假如您对那些有关技术细节的描述缺乏耐心和兴趣那么下面这个简单而直观的比较应该能给您更加深刻的印象。图11使用 zigzag 编码绝对值小的数字无论正负都可以采用较少的 byte 来表示充分利用了 Varint 这种技术。其他的数据类型比如字符串等则采用类似数据库中的 varchar 的表示方法即用一个 varint 表示长度然后将其余部分紧跟在这个长度部分之后即可。5.API介绍常用API, 可以直接查看生成的代码中的 .h 文件1protoc为message的每个required字段和optional字段都定义了以下几个函数不限于这几个TypeName xxx() const; //获取字段的值bool has_xxx(); //判断是否设值void set_xxx(const TypeName); //设值void clear_xxx(); //使其变为默认值2为每个repeated字段定义了以下几个TypeName* add_xxx(); //增加结点, 然后需要拿到结构体指针后对成员进行赋值操作;TypeName xxx(int) const; //获取指定序号的结点类似于C的[]运算符TypeName* mutable_xxx(int); //类似于上一个但是获取的是指针int xxx_size(); //获取结点的数量3下面几个是常用的序列化函数bool SerializeToOstream(std::ostream * output) const; //输出到输出流中bool SerializeToString(string * output) const; //输出到stringbool SerializeToArray(void * data, int size) const; //输出到字节流可以通过ByteSize方法计算存储空间后使用new申请一块内存给data;4与之对应的反序列化函数bool ParseFromIstream(std::istream * input); //从输入流解析bool ParseFromString(const string data); //从string解析bool ParseFromArray(const void * data, int size); //从字节流解析size为buffer的size5其他常用的函数bool IsInitialized(); //检查是否所有required字段都被设值size_t ByteSize() const; //获取二进制字节序列的大小6对嵌套message成员提供的函数bool has_xxx()void set_has_xxx()void clear_has_xxx()void clear_xxx()const TypeName xxx() const //前面几个和上面介绍的一致TypeName* mutable_xxx() //会自动new一块内存并返回,然后拿到结构体指针后对成员进行赋值操作;TypeName* release_xxx()void set_allocated_xxx(TypeName* xxx) //传入的参数需要自己手动new一块内存,和mutable_xxx()有所区别;6.Protobuf的优点6.1 优点1Protobuf有如XML不过它更小、更快、也更简单。你可以定义自己的数据结构然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用Protobuf对数据结构进行一次描述即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。2它有一个非常棒的特性即“向后”兼容性好人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。这样您的程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题。因为添加新的消息中的 field 并不会引起已经发布的程序的任何改变。3Protobuf 语义更清晰无需类似 XML 解析器的东西因为 Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操作。4使用 Protobuf 无需学习复杂的文档对象模型Protobuf 的编程模式比较友好简单易学同时它拥有良好的文档和示例对于喜欢简单事物的人们而言Protobuf 比其他的技术更加有吸引力。6.2 不足之处Protbuf 与 XML 相比也有不足之处。它功能简单无法用来表示复杂的概念。XML 已经成为多种行业标准的编写工具Protobuf 只是 Google 公司内部使用的工具在通用性上还差很多。由于文本并不适合用来描述数据结构所以 Protobuf 也不适合用来对基于文本的标记文档如 HTML建模。另外由于 XML 具有某种程度上的自解释性它可以被人直接读取编辑在这一点上 Protobuf 不行它以二进制的方式存储除非你有 .proto 定义否则你没法直接读出 Protobuf 的任何内容