(上)
1,序列化 : 将数据结构或对象转换成二进制串的过程
反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
2,
3,应用场景
1),传输数据量大 & 网络环境不稳定 的数据存储、RPC 数据交换 的需求场景
如 即时IM (QQ、微信)的需求场景
2),在 传输数据量较大的需求场景下,Protobuf比XML、Json 更小、更快、使用 & 维护更简单!
4,使用
1)AS中就有使用protobuf
Android Studio\lib\protobuf-java-3.4.0.jar
2)官方主页https://developers.google.com/protocol-buffers/docs/javatutorial#compiling-your-protocol-buffers
3)语法指南https://developers.google.com/protocol-buffers/docs/proto
5,编译
编译命令:protoc -I=
DST_DIR $SRC_DIR/Hello.proto
-I 编译源文件的目录
–java_out 编译java代码输出目录
我在文件夹里新创建了一个Hello.proto文件
我们看下
syntax = "proto2";
package tutorial;
option java_package = "com.example.tutorial";
option java_outer_classname = "MsgProtos";
message Msg {
optional string text = 1;
}
syntax = “proto2”;是版本号:
打开cmd 我们cd进该目录D:\汉冠军侯骠骑将军霍去病\Lsn13_Protobuf_2019-5-29(Damon)\Lsn13_Protobuf_2019-5-29(Damon)\资料
输入命令protoc --java_out=. Hello.proto
,原来的文件和输出的文件都是当前目录,
回车后我们发现文件夹里多了一个com文件夹,一直点下去可以看到该目录(D:\汉冠军侯骠骑将军霍去病\Lsn13_Protobuf_2019-5-29(Damon)\Lsn13_Protobuf_2019-5-29(Damon)\资料\com\example\tutorial)下有一个MsgProtos.java文件
6,使用gradle插件
网址:https://github.com/google/protobuf-gradle-plugin
根据该网址的介绍以及结合我下面这张图片你就可以轻松配置:
7,官方有一个例子,我们看下在android studio中怎么使用
syntax = "proto2";
package tutorial;
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
message Person {
注意这里的命名,必须是从1开始依次往后写,这里的1不是name的值,是id
建议不要使用required属性
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 phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
然后rebuild 工程,在下图所示的目录生成java文件
我们在Main方法中测试下
private void protoTest() {
AddressBookProtos.Person.PhoneNumber.Builder builder
= AddressBookProtos.Person.PhoneNumber.newBuilder().setNumber("110");
AddressBookProtos.Person.Builder zs = AddressBookProtos.Person.newBuilder()
.setName("张三")
.setId(1)
.addPhones(builder);
AddressBookProtos.Person.PhoneNumber.Builder builder1
= AddressBookProtos.Person.PhoneNumber.newBuilder().setNumber("120");
AddressBookProtos.Person.Builder ls = AddressBookProtos.Person.newBuilder()
.setName("李四")
.setId(2)
.addPhones(builder1);
AddressBookProtos.AddressBook addressBook = AddressBookProtos.AddressBook.newBuilder()
.addPeople(zs)
.addPeople(ls).build();
long l = System.currentTimeMillis();
byte[] bytes = addressBook.toByteArray();
Log.e(TAG, "protobuf 序列化耗时:" + (System.currentTimeMillis() - l));//输出53
Log.e(TAG, "protobuf 序列化数据大小:" + bytes.length);//输出38
try {
l = System.currentTimeMillis();
AddressBookProtos.AddressBook.parseFrom(bytes);
Log.e(TAG, "protobuf 反序列化耗时:" + (System.currentTimeMillis() - l));//输出43
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
我们在用json测试下
先贴上三个bean类
package com.dn.protobuf.struct;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author Damon
* @Date 2019/05/29 00:05:24
*
*/
public class AddressBook {
List<Person> persons;
public AddressBook() {
this.persons = new ArrayList<>();
}
public void addPersons(Person person) {
persons.add(person);
}
public List<Person> getPersons( ) {
return persons;
}
}
package com.dn.protobuf.struct;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author Damon
* @Date 2019/05/29 00:06:31
*
*/
public class Person {
private String name;
private int id;
private String email;
private List<PhoneNumber> phones;
public Person() {
phones = new ArrayList<>();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public void addPhones(PhoneNumber number) {
phones.add(number);
}
public List<PhoneNumber> getPhones() {
return phones;
}
}
package com.dn.protobuf.struct;
/**
*
* @author Damon
* @Date 2019/05/29 00:07:48
*
*/
public class PhoneNumber {
enum PhoneType {
MOBILE,
HOME,
WORK;
}
private String number;
private PhoneType type;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public PhoneType getType() {
return type;
}
public void setType(PhoneType type) {
this.type = type;
}
}
package com.dn.protobuf.struct;
import android.util.Log;
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
/**
*
* @author Damon
* @Date 2019/05/29 00:09:52
*
*/
public class JsonTest {
private static final String TAG = "JsonTest";
public static void fastJson() {
AddressBook addressBook = getObject();
long l = System.currentTimeMillis();
String data = JSON.toJSONString(addressBook);
byte[] bytes = data.getBytes();
Log.e(TAG, "FastJson 序列化耗时:" + (System.currentTimeMillis() - l));
Log.e(TAG, "FastJson 序列化数据大小:" + bytes.length);
l = System.currentTimeMillis();
AddressBook addressBook1 = JSON.parseObject(new String(bytes), AddressBook.class);
Log.e(TAG, "FastJson 反序列化耗时:" + (System.currentTimeMillis() - l));
// Log.e(TAG,addressBook1.getPersons().get(0).getName());
// Log.e(TAG,addressBook1.getPersons().get(0).getPhones().get(0).getNumber());
}
public static void gson(){
AddressBook addressBook = getObject();
long l = System.currentTimeMillis();
Gson gson = new Gson();
String data = gson.toJson(addressBook);
byte[] bytes = data.getBytes();
Log.e(TAG, "Gson 序列化耗时:" + (System.currentTimeMillis() - l));
Log.e(TAG, "Gson 序列化数据大小:" + bytes.length);
l = System.currentTimeMillis();
AddressBook addressBook1 = gson.fromJson(new String(bytes), AddressBook.class);
Log.e(TAG, "Gson 反序列化耗时:" + (System.currentTimeMillis() - l));
// Log.e(TAG,addressBook1.getPersons().get(0).getName());
// Log.e(TAG,addressBook1.getPersons().get(0).getPhones().get(0).getNumber());
}
private static AddressBook getObject(){
AddressBook addressBook = new AddressBook();
PhoneNumber p_110 = new PhoneNumber();
p_110.setNumber("110");
Person zs = new Person();
zs.setId(1);
zs.setName("张三");
zs.addPhones(p_110);
addressBook.addPersons(zs);
PhoneNumber p_120 = new PhoneNumber();
p_120.setNumber("120");
Person ls = new Person();
ls.setId(2);
ls.setName("李四");
ls.addPhones(p_120);
addressBook.addPersons(ls);
return addressBook;
}
}
同样的文件我们用json测试,发现序列化数据大小是119,完爆json有没有。
下下下下
8,我们向服务器发送30个byte数据
第一种方式
//直接发送数据,但是容易造成粘包
byte[] bytes = "hello".getBytes();
104 101 108 108 111
Log.e(TAG, Arrays.toString(bytes));
byte[] bytes = getByte();
第二种方式
hello 的byte数据是104 101 108 108 111,为了防止粘包的现象,我们在包头和包尾加上108,但这又容易和hello中的108混淆,造成分包现象。我们可以把108进行转义
我们把hello数据中的108 转义为 101 0;
那么数据就变成了 108 104 101 101 0 101 0 111 108
我们把原来数据中的101 在变为101 1;这样我们的数据就变成了108 104 101 1 101 0 101 0 111 108。这样服务器就可以解析了。
第三种方式是使用Protobuf
protobuf实体类
syntax = "proto2";
package tutorial;
option java_package = "com.example.tutorial";
option java_outer_classname = "MsgProtos";
message Msg {
optional string text = 1;
}
我们看下这三种方式
package com.dn.protobuf;
import android.util.Log;
import com.example.tutorial.MsgProtos;
import com.google.protobuf.CodedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
*
* @author Damon
* @Date 2019/05/29 00:17:13
*
*/
public class SocketTest extends Thread {
private static final String TAG = "SocketTest";
private byte[] byte1;
@Override
public void run() {
Socket socket = null;
try {
socket = new Socket();
//连接服务器
socket.connect(new InetSocketAddress("10.0.2.2", 8888));
//获取输出流
OutputStream os = socket.getOutputStream();
//1,直接发送数据,但是容易造成粘包
// byte[] bytes = "hello".getBytes();
// 104 101 108 108 111
// Log.e(TAG, Arrays.toString(bytes));
//2,经过处理后发送,开始和结尾处添加108,可以有效避免粘包分包现象
// byte[] bytes = getByte();
//3,使用protobuf
byte[] bytes = getByte1();
for (int i = 0; i < 30; ++i) {
os.write(bytes);
}
os.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != socket) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 添加包数据标识位
* hello = [104, 101, 108, 108, 111]
* 108,104, 101, 108, 108, 111,108
*/
public byte[] getByte() {
//使用 108 作为包与包之间的分割符
//则 如果包体数据中有108 将其定义为 101,0
// 101则定义为 101,1
//所以最终我们需要发送的数据是
// 108,104,101,1,101,0,101,0,111,108
byte[] bytes = "hello".getBytes();
Log.e(TAG, Arrays.toString(bytes));
//则数据最大长度可能是原数据的2倍+2 (如果数据全是 0,1)
// 101 1
// 101 1 = 101 1 1
ByteBuffer buffer = ByteBuffer.allocate(bytes.length * 2 + 2);//从堆空间中分配缓冲区
//加入包头
buffer.put((byte) 108);
//加入包体数据
for (int i = 0; i < bytes.length; ++i) {
byte aByte = bytes[i];
//108 变成 101,0
if (aByte == 108) {
buffer.put((byte) 101);
buffer.put((byte) 0);
}
//101 变成 101,1
else if (aByte == 101) {
buffer.put((byte) 101);
buffer.put((byte) 1);
} else {
buffer.put(aByte);
}
}
//加入包尾
buffer.put((byte) 108);
//
byte[] data = new byte[buffer.position()];
buffer.flip();//将缓存字节数组的指针设置为数组的开始序列即数组下标0,从buffer开头,对该buffer进行遍历(读取)了
buffer.get(data);
Log.e(TAG, Arrays.toString(data));
return data;
}
/**
* [x],104, 101, 108, 108, 111
*
* @return
*/
public byte[] getByte1() throws IOException {
MsgProtos.Msg msg = MsgProtos.Msg.newBuilder().setText("hello").build();
//在包体前 加入一个包长度字段
byte[] body = msg.toByteArray();
int bodyLen = body.length;
//java int 4个字节
//protobuf 如果来编码这个int 数据 需要占用多少个字节
//t通过CodedOutputStream算出最大占用的字节,这里最大占用5个字节
int headLen = CodedOutputStream.computeUInt32SizeNoTag(bodyLen);
Log.e(TAG, "包头需要占用:" + headLen + "字节");
byte[] head = new byte[headLen];
//将 protobuf 编译的 bodyLen 写入到 head 里面
CodedOutputStream cos = CodedOutputStream.newInstance(head);
cos.writeUInt32NoTag(bodyLen);
cos.flush();
//head 表示包长度
// 包总长度 包头+包体长度
byte[] data = new byte[headLen + bodyLen];
System.arraycopy(head, 0, data, 0, headLen);
System.arraycopy(body, 0, data, headLen, bodyLen);
return data;
}
}
MainActivity中调用
new SocketTest().start();
服务端代码我等下上传