之前一直习惯用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-VERSION−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的安装

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 -version
Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-11T00:41:4
7+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 Corporation
Java home: C:\Program Files\Java\jdk1.7.0_09\jre
Default locale: zh_CN, platform encoding: GBK
OS 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: 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对象呢?

反序列化

我们可以这样

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这样的字眼,它其实是数据类型。

protoc类型 java类型 c++类型
double double double
float float float
int32 int int32
int64 long int64
uint32 int uint32
uint64 long uint64
sint32 int int32
sint64 long int64
fixed32 int uint32
fixed64 long uint64
sfixed32 int uint32
sfixed64 long uint64
bool boolean bool
string String string
bytes ByteString string

最常用的就是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: 123456
com.frank.protocdemo I/System.out: name: "Frank"
com.frank.protocdemo I/System.out: hobby: "music"
com.frank.protocdemo I/System.out: number: 123456
com.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 = 1
com.frank.protocdemo I/System.out: number: 123456
com.frank.protocdemo I/System.out: name: "Frank"
com.frank.protocdemo I/System.out: hobby: "music"
com.frank.protocdemo I/System.out: array: 1
com.frank.protocdemo I/System.out: array: 2
com.frank.protocdemo I/System.out: array: 3
com.frank.protocdemo I/System.out: number: 123456
com.frank.protocdemo I/System.out: name: "Frank"
com.frank.protocdemo I/System.out: hobby: "music"
com.frank.protocdemo I/System.out: array: 1
com.frank.protocdemo I/System.out: array: 2
com.frank.protocdemo I/System.out: array: 3
com.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: 1111111
com.frank.protocdemo I/System.out:   weixin: 222222
com.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: 1111111
com.frank.protocdemo I/System.out:   weixin: 222222
com.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: 99
com.frank.protocdemo I/System.out:   history: 88
com.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: 99
com.frank.protocdemo I/System.out:   history: 88
com.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篇)相关推荐

  1. Protocol Buffer Java应用实例

    生成目标语言代码 下面的命令帮助我们将MyMessage.proto文件中定义的一组Protocol Buffer格式的消息编译成目标语言(Java)的代码.至于消息的内容,我们会在后面以分段的形式逐 ...

  2. Protocol Buffer入门——轻松搭建java环境 .

    http://blog.csdn.net/xyz317100759/article/details/6261236 2011-03-19 15:44 1185人阅读 评论(5) 收藏 举报 由于项目的 ...

  3. Java与C++进行系统间交互:Protocol Buffer

    在一次项目中,因笔者负责的java端应用需要与公司C++系统进行交互,公司选定Protocol Buffer方案,故简单的了解一下 有需要的可以看一下其他作者的文章,了解一下Protobuf: htt ...

  4. 知心王姐小饭桌 IM消息应用开发:一看看懂Protocol Buffer(协议篇)

    前言 由于笔者业团队的业务对即时通讯服务有很大的依赖,春节结束后的第一天,红包没到,产品同学先到了,产品同学和我说要做一款IM,看到需求文档后和设计图后笔者大吃一斤 这不就是一个翻版的web qq吗? ...

  5. c++ java通信 protocol buffer,google protocol buffer (C++,Java序列化应用实例)

    google protocol buffer (C++,Java序列化使用实例) 转载,请注明出处: http://blog.csdn.net/eclipser1987/article/details ...

  6. 学习笔记:Java Protocol Buffer的使用和编码原理学习

    一.protocolbuffer简介: protocol buffer 是 google 的一种数据交换的格式,它独立于语言,独立于平台.google 提供了三种语言的实现:java.c++ 和 py ...

  7. Google Protocol Buffer 的使用和原理

    FROM : https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/ Google Protocol Buffer 的使用和原理 Protocol ...

  8. Protocol Buffer在MCU上的实现--C语言

    一 什么是Protocol Buffer Protocol Buffer是一种支持多平台.多语言.可扩展的的数据序列化机制,相较于XML来说,protobuf更小更快更简单,支持自定义的数据结构,用p ...

  9. Google Protocol Buffer 简单介绍

    以下内容主要整理自官方文档. 为什么使用 Protocol Buffers .proto文件 Protocol Buffers 语法 编译.proto文件 Protocol Buffers API 枚 ...

最新文章

  1. 在解决方案中所使用 NuGet 管理软件包依赖
  2. __cdecl __stdcall区别-转
  3. System.Timers.Timer与System.Windows.Forms.Timer 区别
  4. JZOJ 3899. 【NOIP2014模拟】逻辑的连通性
  5. python学习并发编程
  6. string(STL)
  7. Open Source Blog 开源ASP.NET/C# 博客平台 v2.5 发布(提供源码下载)
  8. android studio8.0,Android Studio错误:(8,0)未找到ID为’android’的插件
  9. selenium 简介
  10. 将一张图片修改为合适的像素大小
  11. 2016蓝桥杯C++A:消除尾一(二进制运算)
  12. 用计算机模拟高空救援的过程是人工智能在,本科-人工智能复习题
  13. 【Gym-100085 E】Eve【模拟题】
  14. 【JAVA视频压缩】-------轻量级视频压缩组件JAVE
  15. 人生有三重境界:看山是山,看水是水;看山不是山,看水不是水;看山还是山,看水还是水(转载)
  16. C++ 3D 绘图技术调研常用库介绍
  17. JavaScript基础(五)——ES2015(ES6)基础语法
  18. 【江枫】用Perl的hash数组实现个性化监控
  19. 如何关闭 window10 自带的杀毒软件
  20. python ip动态代理_Python实现爬取可用代理IP

热门文章

  1. 【skLearn 回归模型】多项式回归 PolynomialFeatures
  2. DB2入门(1)--安装、启动、连接
  3. 考研英语阅读理解做题技巧(2):主旨题
  4. Microsoft Platform SDK Febrary 2003下载(更新VC6的SDK)
  5. 5个月前,如果你没有不屑于刷这份《字节内推+面试宝典》,今天坐到字节Android部门,年薪70w+的人就是你了......
  6. ACdreM-1061 郭式树 没文化真可怕
  7. Google Earth更新北京奥运场馆卫星地图
  8. oauth0 oauth2_通过OAuth(第1部分)访问社交网站,构建启用OAuth的桌面Twitter客户端
  9. Sentinel-2A数据处理
  10. 毕业后到底去学术界还是工业界?杜克大学陈怡然教授亲述5条“小秘籍”