Protocol buffers协议分析

简介

protocol buffers是Google的一个灵活的、高效的、自动化的用于对结构化数据进行序列化的协议,与json、xml相比,protocol buffers序列化后的码流更小、速度更快、操作更简单。你只需要将要被序列化的数据结构定义一次(使用.proto文件定义),便可以使用特别生成的源代码(使用protobuf提供的生成工具protoc)轻松的使用不同的数据流完成对这些结构数据的读写操作,即使你使用不同的语言(protobuf的跨语言支持特性)。你甚至可以更新你的数据结构的定义(就是更新.proto文件内容)而不会破坏依“老”格式编译出来的程序。

使用简介

1、使用PB之前需要先下载个协议编译器,项目地址
https://github.com/protocolbuffers/protobuf.git
你可以选择自己下载源码编译,编译后可以获取到一个protoc.exe的执行文件,当然嫌麻烦可以直接使用已经编译好的执行文件
https://github.com/protocolbuffers/protobuf/releases/download/v3.9.0/protoc-3.9.0-win64.zip

2、PB协议的描述文件以一个 .proto 后缀的文件,我们可以先创建个简单的PB文件
如Interfaces.proto, 内容如下

syntax = "proto3";

message Player{
  int64 id=1;
  string name=2;
  int32 age=3;
  repeated int32 skillIds=4;
}

message可以理解为class,或者包名

3、类型对比

PB JAVA
bool boolean
int32 int
int64 long
double double
float float
string String
map Map
repeat int32 List

4.编译生成目标语言文件
C++

protoc Interfaces.proto --cpp_out=./src

Java

protoc Interfaces.proto --java_out=./src

Python

protoc Interfaces.proto --python_out=./src

C#

protoc Interfaces.proto --csharp_out=./src

序列化对比

下面来对比下PB、 java序列化、json的序列化结果对比
PB

/**
 * @Author hanjie.l
 */
public class PB2Bytes {
    public static void main(String agrs[]) throws InvalidProtocolBufferException {
        byte[] tobytes = tobytes();
        System.out.println("序列化结果:" + Arrays.toString(tobytes));
        System.out.println("长度:" + tobytes.length);
        System.out.println("==================================");
        Interfaces.Player player = toPlayer(tobytes);
        System.out.println("反序列化结果:");
        System.out.println("id:" + player.getId());
        System.out.println("name:" + player.getName());
        System.out.println("age:" + player.getAge());
        System.out.println("skills:" + Arrays.toString(player.getSkillIdsList().toArray()));
    }

    /**
     * 序列化
     */
    public static byte[]  tobytes(){

        //获取构造器
        Interfaces.Player.Builder newBuilder = Interfaces.Player.newBuilder();
        //设置参数
        newBuilder.setId(1)
                .setName("peter")
                .setAge(18)
                .addSkillIds(1001)
                .addSkillIds(1002);
        //构造出player
        Interfaces.Player player = newBuilder.build();
        //序列化成字节
        byte[] byteArray = player.toByteArray();
        return byteArray;
    }


    /**
     * 反序列化
     * @param byteArray
     */
    public static Interfaces.Player toPlayer(byte[] byteArray) throws InvalidProtocolBufferException {
        //解析成player
        Interfaces.Player player = Interfaces.Player.parseFrom(byteArray);
        return player;
    }
}
序列化结果:[8, 1, 18, 5, 112, 101, 116, 101, 114, 24, 18, 34, 4, -23, 7, -22, 7]
长度:17

java序列化

/**
 * @Author hanjie.l
 */
public class Java2Bytes {
    public static void main(String[] args) throws Exception {
        byte[] tobytes = tobytes();
        System.out.println("序列化结果:" + Arrays.toString(tobytes));
        System.out.println("长度:" + tobytes.length);
        System.out.println("================================");
        Player player = toPlayer(tobytes);
        System.out.println("反序列化结果:");
        System.out.println("id:" + player.getId());
        System.out.println("name:" + player.getName());
        System.out.println("age:" + player.getAge());
        System.out.println("skills:" + Arrays.toString(player.getSkillIds().toArray()));
    }

    /**
     * 序列化
     *
     * @throws IOException
     */
    public static byte[] tobytes() throws IOException {
        // 玩家
        Player player = new Player();
        player.setId(1);
        player.setName("peter");
        player.setAge(18);
        player.getSkillIds().add(1001);
        player.getSkillIds().add(1002);

        // 创建对象流
        ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(arrayOutputStream);

        // 写入对象
        objectOutputStream.writeObject(player);

        // 获取字节数组
        byte[] byteArray = arrayOutputStream.toByteArray();
        return byteArray;
    }

    /**
     * 反序列化
     *
     * @param byteArray
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static Player toPlayer(byte[] byteArray) throws Exception {
        // 创建输入流
        ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(byteArray));
        Player player = (Player) inputStream.readObject();
        return player;
    }
}
序列化结果:[-84, -19, 0, 5, 115, 114, 0, 25, 99, 111, 109, 46, 115, 104, 97, 114, 101, 46, 112, 98, 46, 109, 111, 100, 101, 108, 46, 80, 108, 97, 121, 101, 114, 55, 91, 120, -117, 98, -118, 98, -90, 2, 0, 4, 73, 0, 3, 97, 103, 101, 74, 0, 2, 105, 100, 76, 0, 4, 110, 97, 109, 101, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 0, 8, 115, 107, 105, 108, 108, 73, 100, 115, 116, 0, 16, 76, 106, 97, 118, 97, 47, 117, 116, 105, 108, 47, 76, 105, 115, 116, 59, 120, 112, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 1, 116, 0, 5, 112, 101, 116, 101, 114, 115, 114, 0, 19, 106, 97, 118, 97, 46, 117, 116, 105, 108, 46, 65, 114, 114, 97, 121, 76, 105, 115, 116, 120, -127, -46, 29, -103, -57, 97, -99, 3, 0, 1, 73, 0, 4, 115, 105, 122, 101, 120, 112, 0, 0, 0, 2, 119, 4, 0, 0, 0, 2, 115, 114, 0, 17, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 73, 110, 116, 101, 103, 101, 114, 18, -30, -96, -92, -9, -127, -121, 56, 2, 0, 1, 73, 0, 5, 118, 97, 108, 117, 101, 120, 114, 0, 16, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 78, 117, 109, 98, 101, 114, -122, -84, -107, 29, 11, -108, -32, -117, 2, 0, 0, 120, 112, 0, 0, 3, -23, 115, 113, 0, 126, 0, 7, 0, 0, 3, -22, 120]
长度:276

json


/**
 * @Author hanjie.l
 */
public class Json2Bytes {
    public static void main(String[] args) {
        Player player = new Player();
        player.setId(1);
        player.setName("peter");
        player.setAge(18);
        player.getSkillIds().add(1001);
        player.getSkillIds().add(1002);

        byte[] bytes = JSON.toJSONString(player).getBytes();
        System.out.println("序列化结果:" + Arrays.toString(bytes));
        System.out.println("长度:" + bytes.length);
    }
}
序列化结果:[123, 34, 97, 103, 101, 34, 58, 49, 56, 44, 34, 105, 100, 34, 58, 49, 44, 34, 110, 97, 109, 101, 34, 58, 34, 112, 101, 116, 101, 114, 34, 44, 34, 115, 107, 105, 108, 108, 73, 100, 115, 34, 58, 91, 49, 48, 48, 49, 44, 49, 48, 48, 50, 93, 125]
长度:55

可以很明显发现PB的序列化结果要明显优于后两者

PB原理分析

总结了下PB优秀的序列化结果主要归功于以下3大特点
1、将部分对象信息以.proto文件的方式共享于 序列化方和反序列化方,节省了序列化中需要传递的信息

2、key-value结构

3、数字字节伸缩协议
在讲这个之前先了解下,正常序列化的时候是如何序列化的,以一个整形变量 int value=100来举例(小端)

        int value = 100;
        byte[] memory = new byte[4];
        memory[0]     = (byte) value;
        memory[1] = (byte) (value >>> 8);
        memory[2] = (byte) (value >>> 16);
        memory[3] = (byte) (value >>> 24);
        System.out.println(Arrays.toString(memory));// 输出结果: [100, 0, 0, 0]

下面再看下PB的序列化方式

        int value = 100;
        ByteBuffer pbs = ByteBuffer.allocate(5);
        while (true) {
            if ((value & ~0x7F) == 0) {
                pbs.put((byte) value);
                break;
            } else {
                pbs.put((byte) ((value & 0x7F) | 0x80));
                value >>>= 7;
            }
        }
        pbs.flip();
        byte[] bytes = new byte[pbs.remaining()];
        pbs.get(bytes);
        System.out.println(Arrays.toString(bytes));// 输出结果: [100]

PB为了节省空间,用一个字节的第八位来表示是否还有下个字节,所以如果数字比较小,仅需占用1个字节, 当然因为占用了第八位,所以当数字比较大, 比如Integer.MAX_VALUE的时候需要占用额外多出一个字节,所以int类型在PB中占用的字节数是1-5个字节,以此类比long是占1-9个字节,这有点像弹簧,可能拉长也可能压短,所以我称它为数字伸缩协议


说完优点说缺点

通过实际使用会发现PB用起来很麻烦,需要写proto文件,再生成代码,最蛋疼的是代码有将近1k行, 有点蛋疼,我特么只是个简单的javaBean~~~~~

推荐个项目
https://github.com/jhunters/jprotobuf

这个项目引入以后就舒服多了, 你只需要像原来一样专心写自己的javaBean, 用java代码去生成.proto文件

/**
 * @Author hanjie.l
 */
public class JPBPlayer {
    @Protobuf(order = 1, fieldType = FieldType.INT64, description = "玩家id")
    private long id;
    @Protobuf(order = 2, fieldType = FieldType.STRING, description = "玩家名")
    private String name;
    @Protobuf(order = 3, fieldType = FieldType.INT32, description = "年龄")
    private int age;
    @Protobuf(order = 4, fieldType = FieldType.INT32, description = "技能列表")
    private List skillIds=new ArrayList<>();

编解码


/**
 * @Author hanjie.l
 */
public class JPB2Bytes {
    public static void main(String[] args) throws IOException {
        JPBPlayer player = new JPBPlayer();
        player.setId(100);
        player.setName("peter");
        player.setAge(18);
        player.getSkillIds().add(1001);
        player.getSkillIds().add(1002);
        Codec jpbPlayerCodec = ProtobufProxy.create(JPBPlayer.class);
        byte[] bytes = jpbPlayerCodec.encode(player);
        System.out.println("序列化结果:" + Arrays.toString(bytes));
        System.out.println("长度:" + bytes.length);

        long begin = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            jpbPlayerCodec.encode(player);
        }
        System.out.println("耗时:" + (System.currentTimeMillis() - begin));


        System.out.println("==================================");
        JPBPlayer jpbPlayer = jpbPlayerCodec.decode(bytes);
        System.out.println("反序列化结果:");
        System.out.println("id:" + jpbPlayer.getId());
        System.out.println("name:" + jpbPlayer.getName());
        System.out.println("age:" + jpbPlayer.getAge());
        System.out.println("skills:" + Arrays.toString(jpbPlayer.getSkillIds().toArray()));
    }
}

生产proto信息

    System.out.println(ProtobufIDLGenerator.getIDL(JPBPlayer.class));
message JPBPlayer {  
// 玩家id
 int64 id=1;
// 玩家名
 string name=2;
// 年龄
 int32 age=3;
// 技能列表
repeated int32 skillIds=4;
}

你可能感兴趣的:(Protocol buffers协议分析)