赞
踩
protobuf序列化后得到的是二进制数据,相对于纯文本的json和xml等体积更小,传输速度更快; 序列化和反序列化速度也相对较快;protobuf有配套的工具,可以根据proto文件与指定的语言类型自动生成代码;除此之外,protobuf还具有向前和向后的版本兼容性;因此,protobuf可用于系统间高效的数据传输。
protobuf目前存在两个版本protobuf2和protobuf3,proto消息定义在两个版本中存在着较大差异。
protobuf定义时,需要给每个属性添加一个递增的序号,用于标识属性。如: required int32 id = 1; 此时1位id的需要,在序列化和反序列化的过程中,id的身份标识为1。
如: required int32 id = 1;
此时1位id的需要,在序列化和反序列化时,id的身份标识为1.
[1] double 双精度浮点数,对应Java的double类型,默认值为0;
[2] float浮点数,对应Java的float类型,默认值为0;
[3] int32整型,对应Java的int32类型,默认值为0;
[4] int64长整型,对应Java的int64类型,默认值为0;
[5] bool布尔,对应Java的bool类型,默认值为false;
[6] string字符串,对应Java的string类型,默认值为空字符串;
[7] byte字节,对应Java的byte类型,默认为空字节.
protobuf2中每个字段都需要被标注标签,标签有以下三种:
[1] required, 必须存在;
[2] optional,字段可选;
[3] repeated, 字段可以有0个或多个; 对应Java的类型为列表;
message Address {
// 对应Java的 List<String> country;
required string country = 1;
optional string location = 2;
};
说明:required标签标注的字段为必选字段,在反序列化为Java对象时,会对所有的required字段进行非空校验:如果为空,则会抛出异常(注意:序列化时不会抛出异常)。
optional标注的字段,可以进行默认值设置;当反序列化时,如果为空,则使用默认值(注意:序列化时,不会使用默认值),如下所示:
message Person {
// ...
optional bool isMale = 3[default = false];
};
在同一个pb文件中可以使用message定义多个消息, 消息间可以形成依赖。
// 声明文件的protobuf类型,编译器会根据指定的类型使用对应的编译器进行编译 syntax = "proto2"; // 执行编译后生成的java文件所在的包路径;[省略时,不进行路径执行] option java_package = "com.seong"; // 执行编译后生成的java文件的类名;[省略时,为proto文件名称] option java_outer_classname = "TestProtoMsg"; message Person { required int32 id = 1; required string name = 2; required bool isMale = 3; //Person对象拥有一个Address类型的属性 repeated Address address = 4; }; message Address { required string country = 1; optional string location = 2; };
将2.1.3中定义的ptoto文件编译为Java类后,对应的案例如下所示:
public static void main(String[] args) throws InvalidProtocolBufferException { // 构造Person对象(protobuf使用Build设计模式) TestProtoMsg.Person.Builder personBuilder = TestProtoMsg.Person.newBuilder(); personBuilder.setId(1960001001); personBuilder.setName("ue001"); personBuilder.setIsMale(false); TestProtoMsg.Address.Builder addressBuilder = TestProtoMsg.Address.newBuilder(); addressBuilder.setCountry("zh-CN"); addressBuilder.setLocation("NanJing"); personBuilder.addAddress(addressBuilder.build()); TestProtoMsg.Person person = personBuilder.setId(1).build(); // 将Person对象序列化为字节数组,可用于传输 byte[] personBytes = person.toByteArray(); System.out.println("personBytes length is " + personBytes.length); // 将字节数组序列化为Java对象 TestProtoMsg.Person newPerson = TestProtoMsg.Person.parseFrom(personBytes); System.out.println(newPerson); }
输出结果如下所示:
personBytes length is 29
id: 1
name: "ue001"
isMale: false
address {
country: "zh-CN"
location: "NanJing"
}
protobuf3为了提高消息传输效率,在protobuf2基础上进行了如下改动:
[1] 去除了required和option字段,认为所有的字段都是可选的,不设值时取默认值; 同时取消了default选项;
[2] 首行(非注释)使用【syntax = “proto3”;】 在protobuf2中应使用【syntax = “proto2”;】
[3] 枚举类型的第一个字段必须为0;
[4] 引入map类型;map<string,string> 或map<string,int32>
使用protoc命令方式:
protoc --java_out={生成的Java文件存放路径} {proto文件所在路径}
protoc --java_out=.\ Test.proto
使用IDEA开发时,可以安装如下三个插件,用于语法高亮以及自动扫描proto文件和生成Java源码:
[1] Protobuf
[2] GenProtobuf
[3] Protobuf Generator
使用前,需要在IDEA->Tools->Configure GenProtobuf中配置protoc命令的路径和java的生成路径;
配置后,可以运行IDEA->Tools->Generate all Protobufs命令,生成对应的Java源码。
另外,使用Maven/Gradle管理项目时,可以集成对应的插件,在打包时进行自动化管理(略,待补充)。
在2.1.4章节中已介绍了Person对象的构建、序列化和反序列化操作,只需要将这部分操作放在netty的编解码器中即可,唯一变化的地方是字节数组(byte[])与ByteBuf之间的转换。
服务端的解码器:
public class ProtoBufDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
// 使用Protobuf的解码API
TestProtoMsg.Person person = TestProtoMsg.Person.parseFrom(bytes);
list.add(person);
}
}
客户端的编码器:
public class ProtoMsgEncode extends MessageToByteEncoder<TestProtoMsg.Person> {
@Override
protected void encode(ChannelHandlerContext ctx, TestProtoMsg.Person person, ByteBuf byteBuf) {
// 使用Protobuf的编码API
byte[] personBytes = person.toByteArray();
byteBuf.writeBytes(personBytes);
}
}
添加如下测试逻辑:
[1] 客户添加端发送逻辑:
// 客户端发送消息
channel.writeAndFlush(buildPersonMsg());
private TestProtoMsg.Person buildPersonMsg() {
TestProtoMsg.Person.Builder personBuilder = TestProtoMsg.Person.newBuilder();
personBuilder.setId(1960001001);
personBuilder.setName("ue001");
personBuilder.setIsMale(false);
TestProtoMsg.Address.Builder addressBuilder = TestProtoMsg.Address.newBuilder();
addressBuilder.setCountry("zh-CN");
addressBuilder.setLocation("NanJing");
personBuilder.addAddress(addressBuilder.build());
return personBuilder.setId(1).build();
}
[2] 服务端添加Handler:
// 服务端添加测试的Handler:
public class TestProtoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println(msg);
}
}
输出结果如下所示:
四月 15, 2024 09:40:03 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x78242fe6, L:/0:0:0:0:0:0:0:0:9999] READ: [id: 0x5a32ddcd, L:/127.0.0.1:9999 - R:/127.0.0.1:51843]
四月 15, 2024 09:40:03 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0x78242fe6, L:/0:0:0:0:0:0:0:0:9999] READ COMPLETE
id: 1
name: "ue001"
isMale: false
address {
country: "zh-CN"
location: "NanJing"
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。