数据序列化框架——protobuf

数据序列化框架——protobuf

1. 概览

  • 简介
        protobuf全称Protocol Buffers,是Google的数据交换格式(协议),用于将数据序列化,在不同服务器之间进行高效的传输。如果你不是很理解,做个类比,我们平时常用的http、xml、json等,都是一种数据交换协议。
  • 特点
    1. 跨平台、跨语言
    2. 扩展性强
    3. 序列化后的数据量小,传输快
    4. 解析效率很高
  • GitHub地址: https://github.com/protocolbuffers/protobuf

2. 常用的传输方式的一些问题

  • 不同语言、不同服务器之间要传输数据
    java2python
        显然,在Java端服务器将Java自己序列化Person对象得到的字节流,传输到Python端服务器,Python端用自己的方式去解析是行不通的。所以,一般可以使用xml、json、http等数据交换协议。因为字节流转字符流,大家的方式都是一样的,只需要约定好两端字符解析的格式就行了。
  • 传输效率较慢、数据量大、序列化和反序列化效率低
    json
        图中,Java端先将Person对象按照约定的格式编码为json格式字符串,然后进行序列化,再将字节流数据传输到Python端。Python端接收到字节流数据后,进行反序列化,再按照约定的格式解码成Person对象。这是可行的。
        当我们采用json传输格式后,我们的数据交换已经具有了跨平台、跨语言的能力,然而由于网络数据交换量日益增大,将字节流转换为字符流再解析的方式的弊端也暴露出来了:占用空间大、传输效率不够、编码和解码性能不够。
        那如果统一定义一个字节流的解析规范不就好了?但是,每个语言都要编写不同的字节流解析代码(字节流操作太繁琐),每改一次协议的数据格式就得重新写一次字节流的解析代码,估计做解析的程序员就会破口大骂“WTF”!!这个时候,大家都很苦恼,Google的protobuf就此来解决您的问题!
  • protobuf做了什么?
    transform
        首先,Google统一为各个语言平台定义了统一的数据交换格式,大家只需要按照规范编写message.proto文件(进行数据交换的对象)。其次,Google提供了protoc工具程序,用于将message.proto解析成各个语言的代码文件。最后,不同语言平台的程序员只需要拿着各自语言代码的文件,调用该代码的方法对传输过来的字节流进行序列化、反序列化即可!

3. 使用 protobuf 示例

注: 文章顶部有所有相关下载

  1. 下载proroc解析程序
    • GitHub地址:https://github.com/protocolbuffers/protobuf/releases
    • 下载 protoc-3.9.1-win64.zip,解压即可
  2. 编写协议文件 city_message.proto
    // 指定使用哪个版本的语法
    syntax = "proto3";
    
    package pbexample;
    
    // 指定 package路径
    option java_package = "com.skey.pbexample.protocol";
    
    // 生成的类名
    option java_outer_classname = "CityProtocols";
    
    message CityMessage {
    
      // 后面的 1 表示字段名,一个message中所有的字段名都是唯一的
      string name = 1;
    
      // Enum 类型
      enum Season {
        SPRING = 0;
        SUMMER = 1;
        AUTUMN = 2;
        WINTER = 3;
      }
    
      Season season = 2;
    
      // repeated: 声明一个List<Report>
      repeated Report news = 3;
    
    }
    
    message Report {
      // String 类型
      int32 id = 1;
    
      // Map 类型
      map<string, string> kv = 2;
    
    }
    
  3. 使用如下命令,生成CityProtocols.java
    bin\protoc.exe -I=D:\test --java_out=D:\test D:\test\city_message.proto
    
    第一个参数: -I 用于指定--proto_path (协议文件的父目录)
    第二个参数: --java_out 用于指定生成的文件的路径
    第三个参数: 用于指定待解析的协议文件(也可以写成相对父目录的相对路径.\city_message.proto)
    
  4. 将生成的CityProtocols.java拷入项目对应的包路径中
  5. java代码中引入protobuf-java.jar的依赖(注意版本和前面的protoc程序版本对应)
     <dependencies>
         <dependency>
             <groupId>com.google.protobuf</groupId>
             <artifactId>protobuf-java</artifactId>
             <version>3.9.1</version>
         </dependency>
     </dependencies>
    
  • 基本测试示例
    import com.google.protobuf.InvalidProtocolBufferException;
    import com.skey.pbexample.protocol.CityProtocols;
    
    /**
     * Descr: ProtocolBuffer测试
     * Date: 2019/8/20 19:10
     *
     * @author A Lion~
     */
    public class TestPB {
    
        public static void main(String[] args) throws InvalidProtocolBufferException {
            // 构建2个简单的新闻报道
            CityProtocols.Report report1 = CityProtocols.Report.newBuilder()
                    .setId(1)
                    .putKv("Football", "value1")
                    .putKv("Basketball", "value2")
                    .build();
            CityProtocols.Report report2 = CityProtocols.Report.newBuilder()
                    .setId(2)
                    .putKv("ThreeGorges", "value3")
                    .putKv("Taoyuan", "value4")
                    .build();
            // 构建整个城市的新闻
            CityProtocols.CityMessage cityMessage = CityProtocols.CityMessage.newBuilder()
                    .setName("ChongQing")
                    .setSeason(CityProtocols.CityMessage.Season.SUMMER)
                    .addNews(report1)
                    .addNews(report2)
                    .build();
                    
            // 转为字节数组
            byte[] byteArray = cityMessage.toByteArray();
    
            // 解析,将字节数组转为对象
            CityProtocols.CityMessage message = CityProtocols.CityMessage.parseFrom(byteArray);
            System.out.println(message.getName());
            System.out.println(message.getSeason());
            System.out.println(message.getNewsList());
            System.out.println("---------");
            System.out.println(message);
        }
    
    }
    
  • 网络传输示例
    • 服务端代码
    import com.skey.pbexample.protocol.CityProtocols;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * Descr: ProtocolBuffer服务端
     * Date: 2019/8/21 16:17
     *
     * @author A Lion~
     */
    public class ServerDemo {
    
        public static void main(String[] args) throws IOException {
            // 建立Socket服务端
            ServerSocket server = new ServerSocket(23333);
    
            // 循环,一直接收消息
            while (true) {
                // 接收数据
                Socket socket = server.accept();
                InputStream is = socket.getInputStream();
    
                // 解析InputStream,转为CityMessage对象
                CityProtocols.CityMessage message = CityProtocols.CityMessage.parseFrom(is);
                // 打印信息
                System.out.println("---------------------------------------------------------");
                System.out.println(message.getName());
                System.out.println(message.getSeason());
                System.out.println(message.getNewsList());
    
                is.close();
                socket.close();
            }
    
    //        server.close();
        }
    }
    
    • 客户端代码
    import com.skey.pbexample.protocol.CityProtocols;
    
    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.Socket;
    
    /**
     * Descr: ProtocolBuffer客户端
     * Date: 2019/8/21 16:22
     *
     * @author A Lion~
     */
    public class ClientDemo {
    
        public static void main(String[] args) throws IOException {
        	// 构建2个简单的新闻报道
            CityProtocols.Report report1 = CityProtocols.Report.newBuilder()
                    .setId(1)
                    .putKv("Football", "value1")
                    .putKv("Basketball", "value2")
                    .build();
            CityProtocols.Report report2 = CityProtocols.Report.newBuilder()
                    .setId(2)
                    .putKv("ThreeGorges", "value3")
                    .putKv("Taoyuan", "value4")
                    .build();
            // 构建整个城市的新闻
            CityProtocols.CityMessage cityMessage = CityProtocols.CityMessage.newBuilder()
                    .setName("ChongQing")
                    .setSeason(CityProtocols.CityMessage.Season.SUMMER)
                    .addNews(report1)
                    .addNews(report2)
                    .build();
                    
            // 建立Socket连接
            Socket socket = new Socket("127.0.0.1", 23333);
            // 建立连接后获得输出流
            OutputStream outputStream = socket.getOutputStream();
    
            // 转为字节数组
            byte[] byteArray = cityMessage.toByteArray();
    
            // 将数据传输过去
            outputStream.write(byteArray);
    
            outputStream.close();
            socket.close();
        }
    
    }
    
发布了128 篇原创文章 · 获赞 45 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/alionsss/article/details/99974335