当前位置:   article > 正文

IO系列-2 Protobuf使用说明

IO系列-2 Protobuf使用说明

1.protobuf介绍

protobuf序列化后得到的是二进制数据,相对于纯文本的json和xml等体积更小,传输速度更快; 序列化和反序列化速度也相对较快;protobuf有配套的工具,可以根据proto文件与指定的语言类型自动生成代码;除此之外,protobuf还具有向前和向后的版本兼容性;因此,protobuf可用于系统间高效的数据传输。

2.protobuf定义

protobuf目前存在两个版本protobuf2和protobuf3,proto消息定义在两个版本中存在着较大差异。

2.1 protobuf2

protobuf定义时,需要给每个属性添加一个递增的序号,用于标识属性。如: required int32 id = 1; 此时1位id的需要,在序列化和反序列化的过程中,id的身份标识为1。
如: required int32 id = 1; 此时1位id的需要,在序列化和反序列化时,id的身份标识为1.

2.1.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类型,默认为空字节.

2.1.2 标签

protobuf2中每个字段都需要被标注标签,标签有以下三种:
[1] required, 必须存在;
[2] optional,字段可选;
[3] repeated, 字段可以有0个或多个; 对应Java的类型为列表

message Address {
 // 对应Java的 List<String> country;
    required string country = 1;
    optional string location = 2;
};
  • 1
  • 2
  • 3
  • 4
  • 5

说明:required标签标注的字段为必选字段,在反序列化为Java对象时,会对所有的required字段进行非空校验:如果为空,则会抛出异常(注意:序列化时不会抛出异常)。
optional标注的字段,可以进行默认值设置;当反序列化时,如果为空,则使用默认值(注意:序列化时,不会使用默认值),如下所示:

message Person {
 // ...
    optional bool isMale = 3[default = false];
};
  • 1
  • 2
  • 3
  • 4

2.1.3 组合类型

在同一个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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

2.1.4 案例介绍

将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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

输出结果如下所示:

personBytes length is 29
id: 1
name: "ue001"
isMale: false
address {
  country: "zh-CN"
  location: "NanJing"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.2 protobuf3

protobuf3为了提高消息传输效率,在protobuf2基础上进行了如下改动:
[1] 去除了required和option字段,认为所有的字段都是可选的,不设值时取默认值; 同时取消了default选项;
[2] 首行(非注释)使用【syntax = “proto3”;】 在protobuf2中应使用【syntax = “proto2”;】
[3] 枚举类型的第一个字段必须为0;
[4] 引入map类型;map<string,string> 或map<string,int32>

3.编译方式

使用protoc命令方式:

protoc --java_out={生成的Java文件存放路径} {proto文件所在路径} 

protoc --java_out=.\ Test.proto 
  • 1
  • 2
  • 3

使用IDEA开发时,可以安装如下三个插件,用于语法高亮以及自动扫描proto文件和生成Java源码:
[1] Protobuf
[2] GenProtobuf
[3] Protobuf Generator

使用前,需要在IDEA->Tools->Configure GenProtobuf中配置protoc命令的路径和java的生成路径;
配置后,可以运行IDEA->Tools->Generate all Protobufs命令,生成对应的Java源码。

另外,使用Maven/Gradle管理项目时,可以集成对应的插件,在打包时进行自动化管理(略,待补充)。

4.netty使用protobuf

在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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

客户端的编码器:

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

添加如下测试逻辑:
[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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

[2] 服务端添加Handler:

// 服务端添加测试的Handler:
public class TestProtoServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println(msg);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

输出结果如下所示:

四月 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"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/468469
推荐阅读
相关标签
  

闽ICP备14008679号