美文网首页
Protocol buffers协议分析

Protocol buffers协议分析

作者: Jsolo | 来源:发表于2020-11-12 17:39 被阅读0次

    简介

    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<Integer>

    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<Integer> 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<JPBPlayer> 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协议分析

          本文链接:https://www.haomeiwen.com/subject/negebktx.html