前言
目前主流的几种数据交互的格式主要有xml、json、protobuf等等。一般的web项目中,最流行的主要还是json。因为浏览器对于json数据支持非常好,有很多内建的函数支持。xml数据格式在webservice中应用最为广泛,但是相比于json,它的数据更加冗余,因为需要成对的闭合标签。json使用了键值对的方式,不仅压缩了一定的数据空间,同时也具有可读性。protobuf是后起之秀,是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为profobuf是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。
protobuf具有以下几个主要的优点:
1:序列化后体积相比Json和XML很小,适合网络传输
2:支持跨平台多语言
3:消息格式升级和兼容性还不错
4:序列化反序列化速度很快,快于Json的处理速速
在网络中,关于数据的传输使用方面的性能主要有两个方面:数据的网络传输消耗的时间和数据的生成和解析所消耗的时间。
关于json和protobuf的对比,可以参考其他博客:
https://tech.meituan.com/serialization_vs_deserialization.html
http://www.infoq.com/cn/articles/json-is-5-times-faster-than-protobuf
在一个需要大量的数据传输的场景中,如果数据量很大,那么选择protobuf可以明显的减少数据量,减少网络IO,从而减少网络传输所消耗的时间。
那么,学习protobuf的第一步就是如何安装和配置protobuf?
1,下载protobuf的jar包以及编译器
jar包地址:
http://mvnrepository.com/artifact/com.google.protobuf/protobuf-java
如果想自己打包,源码地址:
https://github.com/google/protobuf
编译器地址:
https://github.com/google/protobuf/releases
上面编译器有exe形式的编译程序。
压缩包中有一个protoc.exe,这个是protobuf的编译器
2,配置环境变量
如果你需要在任意目录下都可以调用protobuf的编译器,可以在环境变量中配置path变量,在其中加入protoc.exe的目录。
如下图:
这样的话,就可以使用命令行来进行proto文件的编译了。但是本文主要讲eclipse环境下的protobuf的使用,此处不详述。
配置好了环境变量后,再dos窗口下测试一下是否可以使用
如下:
3,eclispe安装protobuf相关的插件
在eclipse marketplace上下载protobuf插件并安装。
安装后重启eclipse,然后按照下面的方法配置好相应的环境
main标签页定义protoc.exe的路径
options标签页定义生成的java文件的路径
4 protobuf序列化和反序列化
4.1 定义proto文件
proto文件的后缀名为.proto。该文件是一个定义数据格式的文件,相当于一个元数据文件。
proto文件有自己的一套语法,语法的具体规则请查看:
https://developers.google.com/protocol-buffers/
下面用一个简单的例子,演示一下:
定义一个数据文件:addressbook.proto
这个文件是官网上给的一个示例,内容如下:
// [START declaration]
syntax = "proto3";
package tutorial;
// [END declaration]
// [START java_declaration]
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
// [END java_declaration]
// [START messages]
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
}
repeated PhoneNumber phones = 4;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
// [END messages]
利用.proto文件可以生成java文件。这个java文件是编译器生成的,它会生成相应的类。这个类里面会有相应的方法,下面会解释。
注:proto文件的说明
syntax = “proto3”;是用来定义使用的proto的版本。如果不定义的话,按照官网的说法,默认proto2。
package tutorial;生成的类被放置在一个基于 java_package 选项的java package中。如果这个选项缺失,将替代使用 package 定义。例如,如果 .proto 文件包含:
package foo.bar;
那么生成的java类将放置在Java package foo.bar中。不过,如果 .proto 文件也包含一个 java_package 选项,像这样:
package foo.bar;
option java_package = "com.example.foo.bar";
所以上面生成的java文件会在com.example.tutorial的java package中。
option java_outer_classname = “AddressBookProtos”;定义了生成的java类的名字。
message可以把他看做java中的一个类,优点类似于java bean。里面会定义成员变量,成员变量有三种形式,repeated,optional,required。repeated代表该值有多个,optional可选字段,required代表必选字段。具体含义如下:
required: a value for the field must be provided, otherwise the message will be considered "uninitialized". Trying to build an uninitialized message will throw a RuntimeException. Parsing an uninitialized message will throw an IOException. Other than this, a required field behaves exactly like an optional field.
optional: the field may or may not be set. If an optional field value isn't set, a default value is used. For simple types, you can specify your own default value, as we've done for the phone number type in the example. Otherwise, a system default is used: zero for numeric types, the empty string for strings, false for bools. For embedded messages, the default value is always the "default instance" or "prototype" of the message, which has none of its fields set. Calling the accessor to get the value of an optional (or required) field which has not been explicitly set always returns that field's default value.
repeated: the field may be repeated any number of times (including zero). The order of the repeated values will be preserved in the protocol buffer. Think of repeated fields as dynamically sized arrays.
下面是proto文件中数据类型和java中数据类型对应的关系:
4.2 生成一个java文件
由于安装了protobuf插件,该插件会自动编译proto文件,并在相应的package下生成相应的类。
具体的工程目录如下:
本项目是用maven创建的webapp项目。如果读者对maven不是很了解可以可以查看我的另一篇博客。
生成的java文件中的成员变量和方法如下:
每个message里面都会有相应的成员变量和方法,而且protoc会编译之后,成员变量和方法的名字有自己的命名规则,满足驼峰命名规则。
具体的命名规则请查看:
https://skyao.gitbooks.io/learning-proto3/content/reference/java/fileds.html
4.3 序列化和反序列化
package proto;
import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.Person;
import com.example.tutorial.AddressBookProtos.Person.PhoneNumber;
import com.google.protobuf.InvalidProtocolBufferException;
public class ProtoTest {
public static void main(String[] args) {
//序列化
Person.Builder person=Person.newBuilder();
person.setEmail("xxxxxxxx@qq.com").setId(1).setName("张三");
PhoneNumber.Builder number=PhoneNumber.newBuilder();
number.setNumber("XXXXXXXX");
person.addPhones(number);
AddressBook.Builder address=AddressBook.newBuilder();
address.addPeople(person);
address.addPeople(person);
AddressBook book=address.build();
byte[] result=book.toByteArray();//序列化
// //反序列化
AddressBook read;
try {
read = AddressBook.parseFrom(result);
System.out.println(read.getPeopleList().get(0).getNameBytes().toStringUtf8());
//System.out.println(read);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
}
运行后输出:
张三
如果取消注释,运行System.out.println(read);
可以看到输出的结果是:
people {
name: "\345\274\240\344\270\211"
id: 1
email: "xxxxxxxx@qq.com"
phones {
number: "XXXXXXXX"
}
}
people {
name: "\345\274\240\344\270\211"
id: 1
email: "xxxxxxxx@qq.com"
phones {
number: "XXXXXXXX"
}
}
如果需要转成中文,需要使用toStringUtf8()方法。
上面便是整个流程。
5 前后端的交互解析
因为protobuf传输是二进制数据,因此在前后端交互中都需要进行解析。
对于前端的protobuf的解析,参看
http://dcode.io/protobuf.js/
————————————————————————————————————————————
参考:
https://developers.google.com/protocol-buffers/docs/javatutorial
https://github.com/google/protobuf/releases/tag/v3.3.0
http://dcode.io/protobuf.js/
https://github.com/google/protobuf/releases
http://www.infoq.com/cn/articles/json-is-5-times-faster-than-protobuf