数据序列化框架——protobuf
1. 概览
- 简介
protobuf全称Protocol Buffers,是Google的数据交换格式(协议),用于将数据序列化,在不同服务器之间进行高效的传输。如果你不是很理解,做个类比,我们平时常用的http、xml、json等,都是一种数据交换协议。 - 特点
- 跨平台、跨语言
- 扩展性强
- 序列化后的数据量小,传输快
- 解析效率很高
- GitHub地址: https://github.com/protocolbuffers/protobuf
2. 常用的传输方式的一些问题
- 不同语言、不同服务器之间要传输数据
显然,在Java端服务器将Java自己序列化Person对象得到的字节流,传输到Python端服务器,Python端用自己的方式去解析是行不通的。所以,一般可以使用xml、json、http等数据交换协议。因为字节流转字符流,大家的方式都是一样的,只需要约定好两端字符解析的格式就行了。 - 传输效率较慢、数据量大、序列化和反序列化效率低
图中,Java端先将Person对象按照约定的格式编码为json格式字符串,然后进行序列化,再将字节流数据传输到Python端。Python端接收到字节流数据后,进行反序列化,再按照约定的格式解码成Person对象。这是可行的。
当我们采用json传输格式后,我们的数据交换已经具有了跨平台、跨语言的能力,然而由于网络数据交换量日益增大,将字节流转换为字符流再解析的方式的弊端也暴露出来了:占用空间大、传输效率不够、编码和解码性能不够。
那如果统一定义一个字节流的解析规范不就好了?但是,每个语言都要编写不同的字节流解析代码(字节流操作太繁琐),每改一次协议的数据格式就得重新写一次字节流的解析代码,估计做解析的程序员就会破口大骂“WTF”!!这个时候,大家都很苦恼,Google的protobuf就此来解决您的问题! - protobuf做了什么?
首先,Google统一为各个语言平台定义了统一的数据交换格式,大家只需要按照规范编写message.proto文件(进行数据交换的对象)。其次,Google提供了protoc工具程序,用于将message.proto解析成各个语言的代码文件。最后,不同语言平台的程序员只需要拿着各自语言代码的文件,调用该代码的方法对传输过来的字节流进行序列化、反序列化即可!
3. 使用 protobuf 示例
注: 文章顶部有所有相关下载
- 下载proroc解析程序
- GitHub地址:https://github.com/protocolbuffers/protobuf/releases
- 下载 protoc-3.9.1-win64.zip,解压即可
- 编写协议文件 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; }
- 使用如下命令,生成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)
- 将生成的CityProtocols.java拷入项目对应的包路径中
- 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(); } }