通信协议之Protocol buffer(Java篇)

来源:互联网 发布:万户网络官网 编辑:程序博客网 时间:2024/06/06 03:30

今日科技快讯

微信昨日在APP Store推出6.5.2版本。在该版本的微信中,用户给好友发送图,或者在朋友圈发送时,都可以对照片进行简单的编辑,这些基本的编辑功能包括:涂鸦、 添加微信app表情、添加文字、马赛克、裁剪等功能。另外,订阅号不再提示公众号有多少条未读信息,对未读信息统一用红点表示,但仍然可以看见未读消息的数量。

作者简介

本篇是 frank909 的第二篇投稿,介绍了如何在Windows下基于Java语言使用 Protocol buffer。希望大家喜欢。

frank909 的博客地址:

http://blog.csdn.net/briblue

前言

之前一直习惯用 json 进行数据的传输,觉得很方便。来到新公司后发现同事们用的更多的的协议都不是 json,而是 Protocol buffer

这个东西之前没有听说过,不明白同事们为什么放弃好好的 json 不用,用这个。后来了解到经常是设备与设备之间进行通信,而不是设备与服务器做通信。很多设备是 Linux 下 C语言 做核心服务,c来解析 json 比较麻烦。于是决定花些时间来学习这个陌生的协议。

简介

Protocol Buffers(也称protobuf)是Google公司出口的一种独立于开发语言,独立于平台的可扩展的结构化数据序列机制。通俗点来讲它跟 xml 和 json 是一类。是一种数据交互格式协议。

网上有很多它的介绍,主要优点是它是基于二进制的,所以比起结构化的 xml协议 来说,它的体积很少,数据在传输过程中会更快。另外它也支持 c++、Java、Python、PHP、JavaScript 等主流开发语言。

更多信息,你可以在这个它的官方网站查阅相关的资料:

https://developers.google.com/protocol-buffers

Protocol编译安装

protocol 的编译器是C语言编写的,如果你正在使用c++开发,请根据c++安装引导进行安装。

对于不是c++语言的开发者而言,最简单的方式就是从下面的网站下载一个预编译的二进制文件:

https://github.com/google/protobuf/releases

在上面的链接中,你可以按照对应的平台下载zip包:protoc- VERSION−PLATFORM.zip。如 protobuf-java-3.1.0.zip

如果你要寻找之前的版本,通过下面的maven库:

http://repo1.maven.org/maven2/com/google/protobuf/protoc

因为我主要用java开发应用,所以这篇文章重点讲解 protocol 的 java实现。 还有这是在windows上操作

Protocol buffer的安装

通过Maven方式安装

如果你机器上还没有安装maven.请到下面的网站安装

http://maven.apache.org

选择相应的包。我这里选择的是 apache-maven-3.3.9-bin.zip.

添加 maven 到 path

将下载后的 bin目录 添加到 path 中。

然后用 mvn -version 测试一下


说明安装成功

安装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/,然后运行命令 maven 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。

然后编写如下代码:


最终代码会在运行时打印如下信息:

I/System.out: number: 123456
I/System.out: name: "Frank"
I/System.out: hobby: "music"

说明从 .proto 中的 message 到 java 中的 class 转换已经成功。最后:

student.toByteArray();

这个方法会得到 byte[],我们可以将它送到文件流中进行传输,这也是它的终极目的。也就是我们将 protoc 对象序列化成了字节流数据。

大家注意这个 toByteArray() 产生的 byte[]数组,它代表要全部传输的二进制数据,大家可以打印它的大小,有心人可以用 json 实现两样的信息,然后打印json序列化后的数据长度做比较。看看,protocol buffre 是不是更节省内存空间,但在这里,我不做过多探究。

那么如何将字节流数据反序列化成 protoc 对象呢?

反序列化

我们可以这样:

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.0required 被删掉了optional 字符也不能使用,因为默认的域都是 optional 属性。

3.0新语法

syntax = "proto3";或者syntax="proto2";

这个写在 .proto 文件中,用来指定使用 proto3 语法还是使用 proto2 语法。目前 protobuffer 继续支持 proto2。

数据类型

我们在前面的内容中见到了 int32 这样的字眼,它其实是数据类型。


最常用的就是 floatint32boolstringbytes

枚举

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 文件,并添加到工程当中。再进行代码测试。


在上面代码中有这两句:


然后打印的结果如下:


最后一行,把MALE打印出来了。

数组

通过关键字 repeated 实现,看下面代码:


我们通过 repeated int32 array =6; 定义了一个 int[] 类型的数组。而后面 [packed=true] 是因为要优化之前的版本对一些 int32 数据进行字节对齐。

然后我们在java工程中测试:


通过 builder.addArray() 添加数组元素,打印结果如下:


引用另一个message

在java中经常有一个对象包含另一个对象的情况。而在 protocol buffer 中这个也能实现。一个message中能够嵌套另外一个message。

比如在 Student.proto 中添加一个 Info 对象。


然后测试代码为:


打印结果如下:


嵌套

java中有内部类的概念,如:

class Student {
   class Score{    }}

而在 protocol buffer 中同样支持:


上面在 Message Student 定义了 message Score,里面域代表两门成绩。中文和历史。那好,测试代码如下:


打印结果:


可以看到结果也是准确无误的。

import关键字

我们之前的示例中:


message Info 和 message Student 定义在同一个 .proto 文件当中。而在 protocol buffer 中可以通过 import 关键字引入其它 .proto 文件当中的 message。

[Info.proto]


[Student.proto]


我们通过 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文件就在当前目录下。现在我们这样编写代码:


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。现在我们试试这样:


我们添加了这行 option java_outer_classname="StudentDemo"; ,而最终它产生的java文件也不再是 StudentOuterClass.jav a而是 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文件。但有的时候,这些过程是必须的。

更多

每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。

如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。

欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:

原创粉丝点击