通信协议之Protocol buffer(Java篇)
来源:互联网 发布:网络时间校准网址 编辑:程序博客网 时间:2024/06/05 02:34
之前一直习惯用json进行数据的传输,觉得很方便。来到新公司后发现同事们用的更多的的协议都不是json,而是Protocol buffer。这个东西之前没有听说过,不明白同事们为什么放弃好好的json不用,用这个。后来了解到经常是设备与设备之间进行通信,而不是设备与服务器做通信。很多设备是linux下c语言做核心服务,c来解析json比较麻烦。于是决定花些时间来学习这个陌生的协议。
简介
Protocol Buffers(也称protobuf)是Google公司出口的一种独立于开发语言,独立于平台的可扩展的结构化数据序列机制。通俗点来讲它跟xml和json是一类。是一种数据交互格式协议。
网上有很多它的介绍,主要优点是它是基于二进制的,所以比起结构化的xml协议来说,它的体积很少,数据在传输过程中会更快。另外它也支持c++、java、python、php、javascript等主流开发语言。
更多信息,你可以在这个它的官方网站查阅相关的资料
Protocol编译安装
protocol的编译器是C语言编写的,如果你正在使用c++开发,请根据c++安装引导进行安装。
对于不是c++语言的开发者而言,最简单的方式就是从下面的网站下载一个预编译的二进制文件。
https://github.com/google/protobuf/releases
在上面的链接中,你可以按照对应的平台下载zip包:protoc-
如果你要寻找之前的版本,通过下面的maven库:
http://repo1.maven.org/maven2/com/google/protobuf/protoc/
因为我主要用java开发应用,所以这篇文章重点讲解protocol的java实现。 还有这是在windows上操作。
Protocol buffer的安装
1 通过Maven方式安装
如果你机器上还没有安装maven.请到下面的网站安装
http://maven.apache.org/
选择相应的包。我这里选择的是apache-maven-3.3.9-bin.zip.
添加maven到path
将下载后的bin目录添加到path中。
然后用mvn -version测试一下
C:\Users\xx-xxx>mvn -versionApache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-11T00:41:47+08:00)Maven home: E:\xxxxxx\apache-maven-3.3.9-bin\apache-maven-3.3.9\bin\..Java version: 1.7.0_09, vendor: Oracle CorporationJava home: C:\Program Files\Java\jdk1.7.0_09\jreDefault locale: zh_CN, platform encoding: GBKOS name: "windows 7", version: "6.1", arch: "amd64", family: "windows"
说明安装成功
安装protoc
从https://github.com/google/protobuf/releases下载protoc,如protoc-3.1.0-win32.zip,然后解压然后添加到path.
在命令行中测试
protoc --version
正常情况应该打印版本信息,说明添加成功。
将protoc复制到protocol buffer解压的目录。
这一步很重要。
例如之前下载的protobuf-java-3.1.0.zip我将它解压在E盘 E:\xxxx\protobuf-java-3.1.0
,那么E:\xxxx\protobuf-java-3.1.0\protobuf-3.1.0
这个目录就当它是根目录。我用$ROOT表示。
将protoc.exe文件复制到$ROOT/src/
目录下。
然后在命令行中定位到$ROOT/java/
,
然后运行命令mvn package
,
如果一切成功的话,就会在$ROOT/java/core/target/
目录下生成protobuf-java-3.1.0.jar文件。
编译代码
在下载的protobuf-java-3.1.0.zip解压后,我们定义它的根目录为$ROOT。它下面有一个examples文件夹,里面有个adressbook.proto文件,.proto就是protocol buffer的文件格式后缀。
我们将之前生成的protobuf-java-3.1.0.jar复制到该目录下。然后执行下面的命令
protoc --java_out=. addressbook.proto
它就会自动生成com/example/tutorial/AddressBookProtos.java文件。
自此,protoc buffer就完全编译成功,并且能正常运行了。
接下来我讲解它的基本语法
语法
在Android开发中,json运用的很广泛,gson类可以直接将一个java类对象直接序列成json格式的字符串。
在protocol buffer中同样有类似功能的结构。
比如我要定义一个学生类,它包含学号、姓名、性别必要信息,也有兴趣爱好、特长等必要信息。
如果用java的话。大概如下面
class Student{ int number; String name; int sex; String hobby; String skill;}
那么如果用Protocol buffer来定义呢?
这里有一个关键字message。message是消息的意思,等同于java中的class关键字和c中的struct,代表一段数据的封装。
简单示例
首先我们得创建.proto文件。这里为Student.proto
syntax = "proto3";message Student{ int32 number = 1; string name = 2; int32 sex = 3; string hobby = 4; string skill = 5;}
syntax=”proto3”表示运用proto3的语法。而网上的教程大多还是proto2。
proto3提示required限制符不能起作用,默认的就是optional,表示任何域都是可选的。
那好,我们参加教程前面部分将.proto文件转换成.java文件。
protoc --java_out=. Student.proto
结果在当前目录生成了StudentOuterClass.java文件。至于为什么是StudentOuterClass.java这是因为我们在Student.proto没有指定它编译后生成的文件名称,protoc程序默认生成了StudentOuterClass.java,其实这个名字我们可以自定义,文章后面的内容我会介绍。
序列化
在IDE中新建项目,然后添加StudentOuterClass.java文件,并且添加protobuf-java-3.1.0.jar。
然后编写如下代码:
Student.Builder buidler = Student.newBuilder(); buidler.setName("Frank"); buidler.setNumber(123456); buidler.setHobby("music"); Student student = buidler.build(); System.out.println(student.toString());
最终代码会在运行时打印如下信息:
I/System.out: number: 123456I/System.out: name: "Frank"I/System.out: hobby: "music"
说明从.proto中的message到java中的class转换已经成功。
最后
student.toByteArray();
这个方法会得到byte[],我们可以将它送到文件流中进行传输,这也是它的终极目的。也就是我们将protoc对象序列化成了字节流数据。
大家注意这个toByteArray()产生的byte[]数组,它代表要全部传输的二进制数据,大家可以打印它的大小,有心人可以用json实现两样的信息,然后打印json序列化后的数据长度做比较。看看,protocol buffre是不是更节省内存空间,但在这里,我不做过多探究。
那么如何将字节流数据反序列化成protoc对象呢?
反序列化
我们可以这样
byte[] array = student.toByteArray();try { Student student1 = Student.parseFrom(array); System.out.println(student1.toString());} catch (InvalidProtocolBufferException e) { e.printStackTrace();}
array是之前序列化后产生的byte数据,现在通过Student的静态方法parseFrom()可以数据反序列成Student对象。
现在想来这个是不是很简单???
除了开始的阶段编写.proto文件,然后再把.proto文件编译成java文件麻烦点,其余的步骤甚至比json转换的更便利。
message
上一节已经见识过了message,它等同于java中的class关键字和c中的struct关键字。
message Student{}
限定符
在proto2.0版本中有三个限定符
- required 必要的域- optional 可选的- repeated 可重复的(0~N)
其中被required修饰的变量是必不可少的。
optional可有可无。
repeated修饰的就要是数组字段。
而在proto3.0中required被删掉了。optional字符也不能使用,因为默认的域都是optional属性。
3.0新语法
syntax = "proto3";或者syntax="proto2";
这个写在.proto文件中,用来指定使用proto3语法还是使用proto2语法。目前protobuffer继续支持proto2
数据类型
我们在前面的内容中见到了int32这样的字眼,它其实是数据类型。
最常用的就是float、int32、bool string bytes。
枚举
protocol buffer除了支持上面的基本类型外还支持枚举。
关键字是enum
比如我们可以将前文提到的Student对象中的性别用一个枚举代替。
那么将Student.proto文件修改如下:
syntax = "proto3";enum Sex{ MALE = 0; FEMALE = 1;}message Student{ int32 number = 1; string name = 2; Sex sex = 3; string hobby = 4; string skill = 5;}
然后再用protoc.exe编译产生新的java文件,并添加到工程当中。再进行代码测试。
Student.Builder buidler = Student.newBuilder(); buidler.setName("Frank"); buidler.setNumber(123456); buidler.setHobby("music"); //已经可以设置Sex属性了 buidler.setSex(Sex.MALE); Student student = buidler.build(); System.out.println(student.toString()); byte[] array = student.toByteArray(); try { Student student1 = Student.parseFrom(array); System.out.println(student1.toString()); //在这里打印性别的值 System.out.println(student1.getSex().toString()); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); }
在上面代码中有这两句
//设置性别 buidler.setSex(Sex.MALE);//打印性别 System.out.println(student1.getSex().toString());
然后打印的结果如下:
com.frank.protocdemo I/System.out: number: 123456com.frank.protocdemo I/System.out: name: "Frank"com.frank.protocdemo I/System.out: hobby: "music"com.frank.protocdemo I/System.out: number: 123456com.frank.protocdemo I/System.out: name: "Frank"com.frank.protocdemo I/System.out: hobby: "music"com.frank.protocdemo I/System.out: MALE
最后一行,把MALE打印出来了。
数组
通过关键字repeated实现
看下面代码:
syntax = "proto3";enum Sex{ MALE = 0; FEMALE = 1;}message Student{ int32 number = 1; string name = 2; Sex sex = 3; string hobby = 4; string skill = 5; repeated int32 array = 6 [packed=true];}
我们通过repeated int32 array =6;
定义了一个int[]类型的数组。而后面[packed=true]是因为要优化之前的版本对一些int32数据进行字节对齐。
然后我们在java工程中测试
Student.Builder buidler = Student.newBuilder(); buidler.setName("Frank"); buidler.setNumber(123456); buidler.setHobby("music"); buidler.setSex(Sex.MALE); buidler.addArray(1); buidler.addArray(2); buidler.addArray(3); Student student = buidler.build(); System.out.println(student.toString()); byte[] array = student.toByteArray(); try { Student student1 = Student.parseFrom(array); System.out.println(student1.toString()); System.out.println(student1.getSex().toString()); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); }
通过builder.addArray()添加数组元素。
打印结果如下:
com.frank.protocdemo I/System: FinalizerDaemon: finalize objects = 1com.frank.protocdemo I/System.out: number: 123456com.frank.protocdemo I/System.out: name: "Frank"com.frank.protocdemo I/System.out: hobby: "music"com.frank.protocdemo I/System.out: array: 1com.frank.protocdemo I/System.out: array: 2com.frank.protocdemo I/System.out: array: 3com.frank.protocdemo I/System.out: number: 123456com.frank.protocdemo I/System.out: name: "Frank"com.frank.protocdemo I/System.out: hobby: "music"com.frank.protocdemo I/System.out: array: 1com.frank.protocdemo I/System.out: array: 2com.frank.protocdemo I/System.out: array: 3com.frank.protocdemo I/System.out: MALE
引用另一个message
在java中经常有一个对象包含另一个对象的情况。而在protocol buffer中这个也能实现。
一个message中能够嵌套另外一个message。
比如在Student.proto中添加一个Info对象。
message Info{ int32 qq = 1; int32 weixin = 2;}message Student{ int32 number = 1; string name = 2; Sex sex = 3; string hobby = 4; string skill = 5; repeated int32 array = 6; Info info = 7;}
然后测试代码为:
Student.Builder buidler = Student.newBuilder(); buidler.setName("Frank"); Info.Builder infoBuilder = Info.newBuilder(); infoBuilder.setQq(1111111); infoBuilder.setWeixin(222222); buidler.setInfo(infoBuilder); Student student = buidler.build(); System.out.println(student.toString()); byte[] array = student.toByteArray(); try { Student student1 = Student.parseFrom(array); System.out.println(student1.toString()); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); }
打印结果如下:
com.frank.protocdemo I/System.out: name: "Frank"com.frank.protocdemo I/System.out: info {com.frank.protocdemo I/System.out: qq: 1111111com.frank.protocdemo I/System.out: weixin: 222222com.frank.protocdemo I/System.out: }com.frank.protocdemo I/System.out: name: "Frank"com.frank.protocdemo I/System.out: info {com.frank.protocdemo I/System.out: qq: 1111111com.frank.protocdemo I/System.out: weixin: 222222com.frank.protocdemo I/System.out: }
嵌套
java中有内部类的概念,如
class Student{ class Score{ }}
而在protocol buffer中同样支持
message Student{ message Score { int32 chinese = 1; int32 history = 2; } int32 number = 1; string name = 2; Sex sex = 3; string hobby = 4; string skill = 5; repeated int32 array = 6; Info info = 7; Score score = 8;}
上面在Message Student定义了message Score,里面域代表两门成绩。中文和历史。
那好,测试代码如下:
Student.Builder buidler = Student.newBuilder(); buidler.setName("Frank"); Student.Score.Builder scoreBuilder = Student.Score.newBuilder(); scoreBuilder.setChinese(99); scoreBuilder.setHistory(88); buidler.setScore(scoreBuilder); Student student = buidler.build(); System.out.println(student.toString()); byte[] array = student.toByteArray(); try { Student student1 = Student.parseFrom(array); System.out.println(student1.toString()); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); }
打印结果:
com.frank.protocdemo I/System.out: name: "Frank"com.frank.protocdemo I/System.out: score {com.frank.protocdemo I/System.out: chinese: 99com.frank.protocdemo I/System.out: history: 88com.frank.protocdemo I/System.out: }com.frank.protocdemo I/System.out: name: "Frank"com.frank.protocdemo I/System.out: score {com.frank.protocdemo I/System.out: chinese: 99com.frank.protocdemo I/System.out: history: 88com.frank.protocdemo I/System.out: }
可以看到结果也是准确无误的。
import关键字
我们之前的示例中
message Info{ int32 qq = 1; int32 weixin = 2;}message Student{ int32 number = 1; string name = 2; Sex sex = 3; string hobby = 4; string skill = 5; repeated int32 array = 6; Info info = 7;}
message Info和message Student定义在同一个.proto文件当中。而在protocol buffer中可以通过import关键字引入其它.proto文件当中的message。
[Info.proto]
syntax="proto3";option java_package="com.frank.protocdemo";message Info{ int32 qq = 1; int32 weixin = 2;}
[Student.proto]
syntax = "proto3";import "Info.proto";option java_package="com.frank.protocdemo";option java_outer_classname="StudentDemo";enum Sex{ MALE = 0; FEMALE = 1;}message Student{ message Score { int32 chinese = 1; int32 history = 2; } int32 number = 1; string name = 2; Sex sex = 3; string hobby = 4; string skill = 5; repeated int32 array = 6 [packed=true]; Score score = 7; Info info = 8;}
我们通过import "Info.proto";
就算导入了message Info.
然后我们对两个proto文件进行编译
protoc -I=. --java_out=. Info.proto Student.proto
将会产生InfoOuterClass.java与StudentDemo.java。我们可以将这两个文件添加到工程中,就能如此前那样正常使用。自此,import关键字导入其它proto文件中的message就完成了。
option之java_package
文章一开始我们编写Student.proto时没有指定这个选项,所以它编译后生成的java文件就在当前目录下。现在我们这样编写代码
syntax = "proto3";option java_package="com.frank.protocdemo";enum Sex{ MALE = 0; FEMALE = 1;}message Student{ message Score { int32 chinese = 1; int32 history = 2; } int32 number = 1; string name = 2; Sex sex = 3; string hobby = 4; string skill = 5; repeated int32 array = 6 [packed=true]; Score score = 7;}
option java_package=”com.frank.protocdemo”;
添加了这段代码,它的作用是最终会将编译产生的java文件放在com/frank/protocdemo这个目录下。
所以java_package是用来定义编译后产生的文件所在包结构的
package
package与java_package有些不同,java_package是定义编写生成后的java文件所在的目录,而package是对应的java类的包名。
option之java_outer_classname
我们之前的代码中都没有指定Student.proto编译生成的java文件名称,所以它默认的就是StudentOuterClass.java。
现在我们试试这样
syntax = "proto3";option java_package="com.frank.protocdemo";option java_outer_classname="StudentDemo";enum Sex{ MALE = 0; FEMALE = 1;}message Student{ message Score { int32 chinese = 1; int32 history = 2; } int32 number = 1; string name = 2; Sex sex = 3; string hobby = 4; string skill = 5; repeated int32 array = 6 [packed=true]; Score score = 7;}
我们添加了这行option java_outer_classname="StudentDemo";
,而最终它产生的java文件也不再是StudentOuterClass.java而是StudentDemo.java。
所以java_outer_classname是用来定义编译后的java文件名字的。
编译命令
还记得本文开始的地方吗?用
protoc --java_out =. addressbook.proto
将proto文件编译成java文件。
其实它的完整命令如下:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
- proto_path=IMPORT_PATH 默认当前目录
- cpp_out 生成的c++文件目录
- java_out 生成的java文件目录
- pytho_out 生成的python文件目录
–proto_path等同于-I选项,它的意思是等待编译的.proto文件所在的目录,可以指定多个,如果不指定的话默认是当前目录。
path/to/file.proto 等待编译的proto数据文件。
所以
protoc --java_out =. addressbook.proto
就是将addressbook.proto文件编译产生java文件。
总结
protocol buffer的使用还是相对简单点,唯一麻烦的就是多了一个预编译的过程,将.proto文件转换成.java文件。但有的时候,这些过程是必须的。
- 通信协议之Protocol buffer(Java篇)
- 通信协议之Protocol buffer(Java篇)
- 通信协议之Protocol buffer(Java篇)
- Protocol Buffer Basics: Java
- Protocol Buffer Basics: Java
- Protocol Buffer Basics: Java
- Protocol Buffer Basics: Java
- protocol buffer java jar
- Protocol Buffer Basics: Java
- Protocol Buffer 三 简单例子之Java程序
- Protocol Buffers: (6) Protocol Buffer Basics: Java
- Protocol Buffer介绍(Java)
- Protocol Buffer介绍(Java实例)
- Protocol Buffer介绍(Java)
- Java中使用Protocol Buffer
- Protocol Buffer Java应用实例
- protocol buffer
- protocol buffer
- Java多线程问题总结
- xcode自动注释快捷键
- 23种设计模式之——抽象工厂模式
- 树状数组
- eclipse常用的快捷键总结
- 通信协议之Protocol buffer(Java篇)
- Struts2查看源码
- 【解惑】领略Java内部类的“内部”
- UITableView分析
- Oracle闪回技术之一Oracle 11g 利用FlashTable (闪回表)恢复(用delete)误删的数据
- ride日志不显示
- 1000行SQL整理(收藏价值灰常高)
- Java——第六章(异常处理和文件IO流操作)
- C++(0)_新特性