目录
前言
在现有的项目开发中,应用之间大多都再使用Json方式的序列化数据传输,因为其小巧,快速,可读性好等原因,每个开发或多或少都接触过,或在项目开发中作为首选方式。
然而,作为追求极致的从业者,在高并发,对性能要求很高的项目中,哪怕在序列化方面有一点提升,对整体的性能也是有很可观的效果,Protocol Buffer就是Google的开发团队不断追求效率的同时又兼顾性能,空间比的产物,相对Json,在项目有严苛要求序列化操作时,可以优先考虑使用此种方式。
当然,现在很多朋友都有知道Protocol Buffer,但具体怎么使用,开发步骤都不太了解,本文结合项目中的实践,阐述如何使用,并且怎样通过 Java 版本的 Http方式传输接收Protobuf格式的数据。
正文
准备.proto文件
.proto文件类似.json,.xml文件,是Protobuf协议格式的一种文件表示形式,Google提供了相应的工具,可以生成大多主流语言的代码,本文采用Java代码方式,编写相应的代码,就可以按照Protobuf协议格式所序列化的二进制进行应用间的传输。
在项目开发中,如果是自己提供服务,这种格式的文件需要开发根据业务需求编写,如果是调用其他人的服务,一般对方都会提供相应的.proto文件。
假设,项目中已经获取到了person.proto文件,具体的内容如下:
// protobuf 协议
syntax = "proto2";
// 指定转换后java类的包名
option java_package = "com.steven.entity";
// 指定转换后java类名
option java_outer_classname = "PersonProtobuf";
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
具体如何编写以上文件,可以参照Google官方文档谷歌官方编写proto文件的阐述
生成Java文件
这个工具不得不说,Google开发团队也太强大了,作为一个与平台与语言无关的协议,真的需要做到让使用者觉察不到,是需要很大的决心和能力的,但也不得不吐槽下,如果有类似fastjson,gson等,可以跳过.proto文件的编写,Java中直接通过jar包形式,将Java类直接转换成Protobuf协议的二进制流传输就好了!(有可能已经有人实现了相关的转换方式)
但并不影响Java文件的编写,只是此文件是通过Protobuf工具生成,生成的java文件拷贝到项目中即可使用。
工具安装方式,首先进行官网Protobuf生成工具下载
Windows
2,下载完成后进行解压目录(C:\Steven\Protobuf\protoc-3.12.1-win64)
此时已经可以通过工具进行java代码生成了
3,进入bin目录(C:\Steven\Protobuf\protoc-3.12.1-win64\bin)
4,执行指令 ,验证Protobuf工具正常与否
protoc --version
说明可以使用了,当然可以将(C:\Steven\Protobuf\protoc-3.12.1-win64\bin)
设置为环境变量,
否则到其他文件夹下,会找不到指令
5,执行指令,完成java文件的生成
protoc person.proto --java_out=.
这样会成功生成java文件(C:\Steven\Protobuf\com\steven\entity\PersonProtobuf.java)
有没有发现,这里的包名+类名,就是proto文件中定义的,如果不写,将默认为.proto文件名称为类名
// 指定转换后java类的包名
option java_package = "com.steven.entity";
// 指定转换后java类名
option java_outer_classname = "PersonProtobuf";
注:如果报如下错误,只要命令执行的路径为person.proto文件路径就可以解决了!
Mac
需要下载文件protobuf-java-3.12.1.tar.gz
解压后,进入文件夹protobuf-java-3.12.1执行
make
make install
然后就可以通过上面一样的指令执行,进行java代码生成了!
使用生成的Java文件进行Http数据传输
1,新建Java项目protobuf,将java文件拷贝到相应的包路径下;
此时需要引入protobuf-java的包,否则会发生类找不到的异常。
注意:最好引入的protobuf-java jar 版本与工具的版本一致,至少一个大版本(3.12.x),如下:
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.12.0</version>
</dependency>
2,Http传输解析Person Protobuf
思路:
客户端:生成Person实体内容,通过http,将Protobuf二进制流传输到服务端
服务端:服务端将传输内容进行解析,进行业务逻辑处理,并将处理后的Person同样的方式返回给客户端
代码可以如下编写:
package com.steven;
import com.steven.entity.PersonProtobuf;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
public class ProtobufTest extends TestCase {
public ProtobufTest(String testName) {
super(testName);
}
public static Test suite() {
return new TestSuite(ProtobufTest.class);
}
public void testPersonProtobuf() throws Exception {
PersonProtobuf.Person.PhoneNumber phoneNumber = PersonProtobuf.Person.PhoneNumber.newBuilder()
.setNumber("13136056132")
.setType(PersonProtobuf.Person.PhoneType.MOBILE)
.build();
PersonProtobuf.Person personRequest = PersonProtobuf.Person.newBuilder()
.setId(1)
.setName("Steven")
.setEmail("steven@qq.com")
.addPhone(phoneNumber)
.build();
System.out.println("请求实体:");
System.out.println(personRequest);
// 进行Http传输byte字节流
byte[] bytes = personRequest.toByteArray();
// http sent protobuf and get person protobuf
InputStream inputStream = new ByteArrayInputStream(bytes);
PersonProtobuf.Person personResponse = PersonProtobuf.Person.parseFrom(inputStream);
System.out.println("响应实体:");
System.out.println(personResponse);
}
}
执行结果如下:
name: "Steven"
id: 1
email: "steven@qq.com"
phone {
number: "13136056132"
type: MOBILE
}
响应实体:
name: "Lisa"
id: 1
email: "lisa@qq.com"
phone {
number: "13186056360"
type: MOBILE
}
请求实体和响应实体需要根据业务进行定义和使用,整体的实现可能有差别,但整体的思路和步骤就是如此,是不是很简单!
Http方式的数据传输
本文采用Apache http工具包
引入Jar包
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.8</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.5</version>
</dependency>
代码改写为:
package com.steven;
import com.steven.entity.PersonProtobuf;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.apache.http.HttpEntity;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ProtobufTest extends TestCase {
public ProtobufTest(String testName) {
super(testName);
}
public static Test suite() {
return new TestSuite(ProtobufTest.class);
}
public void testPersonProtobuf() throws Exception {
PersonProtobuf.Person.PhoneNumber phoneNumber = PersonProtobuf.Person.PhoneNumber.newBuilder()
.setNumber("13136056132")
.setType(PersonProtobuf.Person.PhoneType.MOBILE)
.build();
PersonProtobuf.Person personRequest = PersonProtobuf.Person.newBuilder()
.setId(1)
.setName("Steven")
.setEmail("steven@qq.com")
.addPhone(phoneNumber)
.build();
System.out.println("请求实体:");
System.out.println(personRequest);
// 进行Http传输byte字节流
byte[] bytes = personRequest.toByteArray();
// http sent protobuf and get person protobuf
HttpPost post = new HttpPost("http://www.steven.com/protobuf");
// 建立一个NameValuePair数组,用于存储欲传送的参数
post.setHeader("Content-type", "application/x-protobuf ");
// 请求配置
RequestConfig requestConfig = RequestConfig.custom()
// 与服务器连接超时时间:httpClient 会创建一个异步线程用以创建socket连接,此处设置该socket的连接超时时间
.setConnectTimeout(1000)
// socket 读数据超时时间:从服务器获取响应数据的超时时间
.setSocketTimeout(10000).build();
post.setConfig(requestConfig);
ByteArrayEntity bae = new ByteArrayEntity(bytes);
post.setEntity(bae);
String data = HttpClients.createDefault().execute(post, generalResponseHandler());
PersonProtobuf.Person personResponse = PersonProtobuf.Person.parseFrom(data.getBytes("ISO-8859-1"));
System.out.println("响应实体:");
System.out.println(personResponse);
}
private static ResponseHandler<String> generalResponseHandler() {
return new BasicResponseHandler() {
@Override
public String handleEntity(HttpEntity entity) throws IOException {
return EntityUtils.toString(entity, "ISO-8859-1");
}
};
}
}
收尾
在开发实践中,时不时会遇到Protobuf方式的数据传输,是否通过本文,可以更好的了解如何去使用这样的一种开发方式,在此预祝大家学习愉快!!!