目录:
1.需求:
2.字段规则
消息的字段可以⽤下⾯⼏种规则来修饰:
我们在 src/main/proto/proto3 ⽬录下新建 contacts.proto ⽂件,内容如下:
syntax = "proto3";
package start;
option java_multiple_files = true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package = "com.example.start"; // 编译后⽣成⽂件所在的包路径
option java_outer_classname = "ContactsProtos"; // 编译后⽣成的proto包装类的类名
message PeopleInfo{
string name = 1;
int32 age = 2;
repeated string phone_numbers = 3;
}
3.消息类型的定义与使⽤
定义:
使⽤
contacts.proto
例如Phone消息定义在phone.proto⽂件中:
syntax = "proto3";
package phone;
option java_multiple_files = true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package = "com.example.start"; // 编译后⽣成⽂件所在的包路径
option java_outer_classname = "PhoneProtos"; // 编译后⽣成的proto包装类的类名
message Phone{
string number = 1;
}
contacts.proto
syntax = "proto3";
package start;
option java_multiple_files = true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package = "com.example.start"; // 编译后⽣成⽂件所在的包路径
option java_outer_classname = "ContactsProtos"; // 编译后⽣成的proto包装类的类名
import "start/phone.proto";
message PeopleInfo{
string name = 1;
int32 age = 2;
repeated phone.Phone phone = 3;
}
运行结果:
3.创建通讯录2.0版本
通讯录2.x的需求是向⽂件中写⼊通讯录列表,以上我们只是定义了⼀个联系⼈的消息,并不能存放通讯录列表,所以还需要在完善⼀下contacts.proto(终版通讯录2.0):
syntax = "proto3";
package start;
option java_multiple_files = true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package = "com.example.start"; // 编译后⽣成⽂件所在的包路径
option java_outer_classname = "ContactsProtos"; // 编译后⽣成的proto包装类的类名
message PeopleInfo{
string name = 1;
int32 age = 2;
message Phone{
string number = 1;
}
repeated Phone phone = 3;
}
message Contacts{
repeated PeopleInfo contacts = 1;
}
接着使⽤maven插件进⾏⼀次编译,这次编译会多⽣成五个⽂件: Contacts.java
ContactsOrBuilder.java ContactsProtos.java PeopleInfo.java PeopleInfoOrBuilder.java 。
可以看出由于我们设置了option java_multiple_files = true; ,会给⽣成的每个⾃定义
message 类都⽣成两个对应的⽂件:。
在message 类中,主要包含:
在 Builder 类中,主要包含:
且在上述的例⼦中:
4.通讯录2.0的写⼊实现
TestWrite.java(通讯录2.0)
package testcode;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class TestWrite {
public static void main(String[] args) throws IOException {
Contacts.Builder contactsBuilder = Contacts.newBuilder();
// 读取已存在的contacts
try {
contactsBuilder.mergeFrom(new
FileInputStream("src/main/java/com/example/start/contacts.bin"));
} catch (FileNotFoundException e) {
System.out.println("contacts.bin not found. Creating a new file.");
}
// 新增⼀个联系⼈
contactsBuilder.addContacts(addPeopleInfo());
// 将新的contacts写回磁盘
FileOutputStream output = new FileOutputStream("src/main/java/com/example/start/contacts.bin");
contactsBuilder.build().writeTo(output);
output.close();
}
private static PeopleInfo addPeopleInfo() {
Scanner scan = new Scanner(System.in);
PeopleInfo.Builder peopleBuilder = PeopleInfo.newBuilder();
System.out.println("-------------新增联系⼈-------------");
System.out.print("请输⼊联系⼈姓名: ");
String name = scan.nextLine();
peopleBuilder.setName(name);
System.out.print("请输⼊联系⼈年龄: ");
int age = scan.nextInt();
peopleBuilder.setAge(age);
scan.nextLine();
for (int i = 0; ; i++) {
System.out.print("请输⼊联系⼈电话" + (i + 1) + "(只输⼊回⻋完成电话新 增): ");
String number = scan.nextLine();
if (number.isEmpty()) {
break;
}
PeopleInfo.Phone.Builder phoneBuilder = PeopleInfo.Phone.newBuilder();
phoneBuilder.setNumber(number);
peopleBuilder.addPhone(phoneBuilder);
}
System.out.println("-------------添加联系⼈成功-------------");
return peopleBuilder.build();
}
}
运行结果:
5.TestRead.java(通讯录2.0)
package testcode;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import java.io.FileInputStream;
import java.io.IOException;
public class TestRead {
public static void main(String[] args) throws IOException {
// 从磁盘⽂件⾥读取,并反序列化为 Message 实例
Contacts contacts = Contacts.parseFrom(
new FileInputStream("src/main/java/com/example/start/contacts.bin"));
// 打印
printContacts(contacts);
}
private static void printContacts(Contacts contacts) {
for (int i = 0; i < contacts.getContactsCount(); i++) {
System.out.println("--------------联系⼈" + (i + 1) + "-----------");
PeopleInfo peopleInfo = contacts.getContacts(i);
System.out.println("姓名: " + peopleInfo.getName());
System.out.println("年龄: " + peopleInfo.getAge());
int j = 1;
for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {
System.out.println("电话" + (j++) + ": " + phone.getNumber());
}
}
}
}
运行结果:
6.TestRead.java(通讯录2.0) 另⼀种验证⽅法--toString()
在⾃定义消息类的⽗抽象类AbstractMessage中,重写了toString()⽅法。该⽅法返回的内容是⼈类可读的,对于调试特别有⽤。例如在TestRead类的main函数中调⽤⼀下:
package testcode;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import java.io.FileInputStream;
import java.io.IOException;
public class TestRead {
public static void main(String[] args) throws IOException {
// 从磁盘⽂件⾥读取,并反序列化为 Message 实例
Contacts contacts = Contacts.parseFrom(
new FileInputStream("src/main/java/com/example/start/contacts.bin"));
// 打印
// printContacts(contacts);
System.out.println(contacts.toString());
}
//
// private static void printContacts(Contacts contacts) {
// for (int i = 0; i < contacts.getContactsCount(); i++) {
// System.out.println("--------------联系⼈" + (i + 1) + "-----------");
// PeopleInfo peopleInfo = contacts.getContacts(i);
// System.out.println("姓名: " + peopleInfo.getName());
// System.out.println("年龄: " + peopleInfo.getAge());
// int j = 1;
// for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {
// System.out.println("电话" + (j++) + ": " + phone.getNumber());
// }
// }
// }
}
运行结果:在这⾥是将utf-8汉字转为⼋进制格式输出了
7. enum类型
定义规则
语法⽀持我们定义枚举类型并使⽤。在.proto⽂件中枚举类型的书写规范为:
枚举类型名称:
使⽤驼峰命名法,⾸字⺟⼤写。例如: MyEnum
常量值名称:
全⼤写字⺟,多个字⺟之间⽤ _ 连接。例如: ENUM_CONST = 0;
我们可以定义⼀个名为PhoneType的枚举类型,定义如下:
enum PhoneType {
MP = 0; //移动电话
TEL = l; //固定电话
}
要注意枚举类型的定义有以下⼏种规则:
定义时注意
将两个具有相同枚举值名称的枚举类型放在单个.proto⽂件下测试时,编译后会报错:某某某常
量已经被定义!所以这⾥要注意:
8.升级通讯录⾄2.1版本
更新contacts.proto(通讯录2.1),新增枚举字段并使⽤,更新内容如下:
syntax = "proto3";
package start;
option java_multiple_files = true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package = "com.example.start"; // 编译后⽣成⽂件所在的包路径
option java_outer_classname = "ContactsProtos"; // 编译后⽣成的proto包装类的类名
message PeopleInfo{
string name = 1;
int32 age = 2;
message Phone {
string number = 1; // 电话号码
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
PhoneType type = 2; // 类型
}
repeated Phone phone = 3; // 电话
}
message Contacts{
repeated PeopleInfo contacts = 1;
}
接着使⽤maven插件进⾏⼀次编译。
更新TestWrite.java(通讯录2.1)
package testcode;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class TestWrite {
public static void main(String[] args) throws IOException {
Contacts.Builder contactsBuilder = Contacts.newBuilder();
// 读取已存在的contacts
try {
contactsBuilder.mergeFrom(new
FileInputStream("src/main/java/com/example/start/contacts.bin"));
} catch (FileNotFoundException e) {
System.out.println("contacts.bin not found. Creating a new file.");
}
// 新增⼀个联系⼈
contactsBuilder.addContacts(addPeopleInfo());
// 将新的contacts写回磁盘
FileOutputStream output = new FileOutputStream("src/main/java/com/example/start/contacts.bin");
contactsBuilder.build().writeTo(output);
output.close();
}
private static PeopleInfo addPeopleInfo() {
Scanner scan = new Scanner(System.in);
PeopleInfo.Builder peopleBuilder = PeopleInfo.newBuilder();
System.out.println("-------------新增联系⼈-------------");
System.out.print("请输⼊联系⼈姓名: ");
String name = scan.nextLine();
peopleBuilder.setName(name);
System.out.print("请输⼊联系⼈年龄: ");
int age = scan.nextInt();
peopleBuilder.setAge(age);
scan.nextLine();
for (int i = 0; ; i++) {
System.out.print("请输⼊联系⼈电话" + (i + 1) + "(只输⼊回⻋完成电话新 增): ");
String number = scan.nextLine();
if (number.isEmpty()) {
break;
}
PeopleInfo.Phone.Builder phoneBuilder = PeopleInfo.Phone.newBuilder();
phoneBuilder.setNumber(number);
System.out.print("选择此电话类型 (1、移动电话 2、固定电话) : ");
int type = scan.nextInt();
scan.nextLine();
switch (type) {
case 1:
phoneBuilder.setType(PeopleInfo.Phone.PhoneType.MP);
break;
case 2:
phoneBuilder.setType(PeopleInfo.Phone.PhoneType.TEL);
break;
default:
System.out.println("⾮法选择,使⽤默认值!");
break;
}
peopleBuilder.addPhone(phoneBuilder);
}
System.out.println("-------------添加联系⼈成功-------------");
return peopleBuilder.build();
}
}
运行结果:
更新TestRead.java(通讯录2.1)
package testcode;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import java.io.FileInputStream;
import java.io.IOException;
public class TestRead {
public static void main(String[] args) throws IOException {
// 从磁盘⽂件⾥读取,并反序列化为 Message 实例
Contacts contacts = Contacts.parseFrom(
new FileInputStream("src/main/java/com/example/start/contacts.bin"));
// 打印
printContacts(contacts);
//System.out.println(contacts.toString());
}
private static void printContacts(Contacts contacts) {
for (int i = 0; i < contacts.getContactsCount(); i++) {
System.out.println("--------------联系⼈" + (i + 1) + "-----------");
PeopleInfo peopleInfo = contacts.getContacts(i);
System.out.println("姓名: " + peopleInfo.getName());
System.out.println("年龄: " + peopleInfo.getAge());
int j = 1;
for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {
System.out.println("电话" + (j++) + ": " + phone.getNumber() + " (" + phone.getType().name() + ")");
}
}
}
}
运行结果:
9.Any类型
字段还可以声明为Any类型,可以理解为泛型类型。使⽤时可以在Any中存储任意消息类型。Any类型的字段也⽤repeated来修饰。Any类型是google已经帮我们定义好的类型,在装ProtoBu时,其中的include⽬录下查找所有google已经定义好的.proto⽂件。
升级通讯录⾄2.2版本
通讯录2.2版本会新增联系⼈的地址信息,我们可以使⽤any类型的字段来存储地址信息。
更新contacts.proto(通讯录2.2),更新内容如下:
contacts.proto
syntax = "proto3";
package start;
option java_multiple_files = true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package = "com.example.start"; // 编译后⽣成⽂件所在的包路径
option java_outer_classname = "ContactsProtos"; // 编译后⽣成的proto包装类的类名
import "google/protobuf/any.proto";
message PeopleInfo{
string name = 1;
int32 age = 2;
message Phone {
string number = 1; // 电话号码
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
PhoneType type = 2; // 类型
}
repeated Phone phone = 3; // 电话
google.protobuf.Any data = 4;
}
message Contacts{
repeated PeopleInfo contacts = 1;
}
message Address{
string home_address = 1;
string unit_address = 2;
}
TestWrite.java
package testcode;
import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.Any;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class TestWrite {
public static void main(String[] args) throws IOException {
Contacts.Builder contactsBuilder = Contacts.newBuilder();
// 读取已存在的contacts
try {
contactsBuilder.mergeFrom(new
FileInputStream("src/main/java/com/example/start/contacts.bin"));
} catch (FileNotFoundException e) {
System.out.println("contacts.bin not found. Creating a new file.");
}
// 新增⼀个联系⼈
contactsBuilder.addContacts(addPeopleInfo());
// 将新的contacts写回磁盘
FileOutputStream output = new FileOutputStream("src/main/java/com/example/start/contacts.bin");
contactsBuilder.build().writeTo(output);
output.close();
}
private static PeopleInfo addPeopleInfo() {
Scanner scan = new Scanner(System.in);
PeopleInfo.Builder peopleBuilder = PeopleInfo.newBuilder();
System.out.println("-------------新增联系⼈-------------");
System.out.print("请输⼊联系⼈姓名: ");
String name = scan.nextLine();
peopleBuilder.setName(name);
System.out.print("请输⼊联系⼈年龄: ");
int age = scan.nextInt();
peopleBuilder.setAge(age);
scan.nextLine();
for (int i = 0; ; i++) {
System.out.print("请输⼊联系⼈电话" + (i + 1) + "(只输⼊回⻋完成电话新 增): ");
String number = scan.nextLine();
if (number.isEmpty()) {
break;
}
PeopleInfo.Phone.Builder phoneBuilder = PeopleInfo.Phone.newBuilder();
phoneBuilder.setNumber(number);
System.out.print("选择此电话类型 (1、移动电话 2、固定电话) : ");
int type = scan.nextInt();
scan.nextLine();
switch (type) {
case 1:
phoneBuilder.setType(PeopleInfo.Phone.PhoneType.MP);
break;
case 2:
phoneBuilder.setType(PeopleInfo.Phone.PhoneType.TEL);
break;
default:
System.out.println("⾮法选择,使⽤默认值!");
break;
}
peopleBuilder.addPhone(phoneBuilder);
}
Address.Builder addressBulider = Address.newBuilder();
System.out.println("请输入联系人家庭地址:");
String homeAddress = scan.nextLine();
addressBulider.setHomeAddress(homeAddress);
System.out.println("请输入联系人单位地址");
String unitAddress = scan.nextLine();
addressBulider.setUnitAddress(unitAddress);
peopleBuilder.setData(Any.pack(addressBulider.build()));
System.out.println("-------------添加联系⼈成功-------------");
return peopleBuilder.build();
}
}
TestRead.java
package testcode;
import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.FileInputStream;
import java.io.IOException;
public class TestRead {
public static void main(String[] args) throws IOException {
// 从磁盘⽂件⾥读取,并反序列化为 Message 实例
Contacts contacts = Contacts.parseFrom(
new FileInputStream("src/main/java/com/example/start/contacts.bin"));
// 打印
printContacts(contacts);
//System.out.println(contacts.toString());
}
private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {
for (int i = 0; i < contacts.getContactsCount(); i++) {
System.out.println("--------------联系⼈" + (i + 1) + "-----------");
PeopleInfo peopleInfo = contacts.getContacts(i);
System.out.println("姓名: " + peopleInfo.getName());
System.out.println("年龄: " + peopleInfo.getAge());
int j = 1;
for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {
System.out.println("电话" + (j++) + ": " + phone.getNumber() + " (" + phone.getType().name() + ")");
}
if (peopleInfo.hasData() && peopleInfo.getData().is(Address.class)) {
Address address = peopleInfo.getData().unpack(Address.class);
if (!address.getHomeAddress().isEmpty()) {
System.out.println("家庭地址:" + address.getHomeAddress());
}
if (!address.getUnitAddress().isEmpty()) {
System.out.println("单位地址:" + address.getUnitAddress());
}
}
}
}
}
运行结果:
10.oneof类型
如果消息中有很多可选字段,并且将来同时只有⼀个字段会被设置,那么就可以使⽤ oneof 加强这个⾏为,也能有节约内存的效果。
升级通讯录⾄2.3版本
通讯录2.3版本想新增联系⼈的其他联系⽅式,⽐如qq或者微信号⼆选⼀,我们就可以使⽤oneof字
段来加强多选⼀这个⾏为。oneof字段定义的格式为: oneof 字段名 { 字段1; 字段2; ... } 更新contacts.proto(通讯录2.3),更新内容如下:
syntax = "proto3";
package start;
option java_multiple_files = true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package = "com.example.start"; // 编译后⽣成⽂件所在的包路径
option java_outer_classname = "ContactsProtos"; // 编译后⽣成的proto包装类的类名
import "google/protobuf/any.proto";
message PeopleInfo{
string name = 1;
int32 age = 2;
message Phone {
string number = 1; // 电话号码
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
PhoneType type = 2; // 类型
}
repeated Phone phone = 3; // 电话
google.protobuf.Any data = 4;
oneof other_contact{
string qq = 5;
string wechat = 6;
}
}
message Contacts{
repeated PeopleInfo contacts = 1;
}
message Address{
string home_address = 1;
string unit_address = 2;
}
TestWrite.java
package testcode;
import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.Any;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class TestWrite {
public static void main(String[] args) throws IOException {
Contacts.Builder contactsBuilder = Contacts.newBuilder();
// 读取已存在的contacts
try {
contactsBuilder.mergeFrom(new
FileInputStream("src/main/java/com/example/start/contacts.bin"));
} catch (FileNotFoundException e) {
System.out.println("contacts.bin not found. Creating a new file.");
}
// 新增⼀个联系⼈
contactsBuilder.addContacts(addPeopleInfo());
// 将新的contacts写回磁盘
FileOutputStream output = new FileOutputStream("src/main/java/com/example/start/contacts.bin");
contactsBuilder.build().writeTo(output);
output.close();
}
private static PeopleInfo addPeopleInfo() {
Scanner scan = new Scanner(System.in);
PeopleInfo.Builder peopleBuilder = PeopleInfo.newBuilder();
System.out.println("-------------新增联系⼈-------------");
System.out.print("请输⼊联系⼈姓名: ");
String name = scan.nextLine();
peopleBuilder.setName(name);
System.out.print("请输⼊联系⼈年龄: ");
int age = scan.nextInt();
peopleBuilder.setAge(age);
scan.nextLine();
for (int i = 0; ; i++) {
System.out.print("请输⼊联系⼈电话" + (i + 1) + "(只输⼊回⻋完成电话新 增): ");
String number = scan.nextLine();
if (number.isEmpty()) {
break;
}
PeopleInfo.Phone.Builder phoneBuilder = PeopleInfo.Phone.newBuilder();
phoneBuilder.setNumber(number);
System.out.print("选择此电话类型 (1、移动电话 2、固定电话) : ");
int type = scan.nextInt();
scan.nextLine();
switch (type) {
case 1:
phoneBuilder.setType(PeopleInfo.Phone.PhoneType.MP);
break;
case 2:
phoneBuilder.setType(PeopleInfo.Phone.PhoneType.TEL);
break;
default:
System.out.println("⾮法选择,使⽤默认值!");
break;
}
peopleBuilder.addPhone(phoneBuilder);
}
Address.Builder addressBulider = Address.newBuilder();
System.out.println("请输入联系人家庭地址:");
String homeAddress = scan.nextLine();
addressBulider.setHomeAddress(homeAddress);
System.out.println("请输入联系人单位地址");
String unitAddress = scan.nextLine();
addressBulider.setUnitAddress(unitAddress);
peopleBuilder.setData(Any.pack(addressBulider.build()));
System.out.println("请选择要添加的其他联系方式(1.qq号 2.微信号):");
int otherContact = scan.nextInt();
scan.nextLine();
if (1 == otherContact) {
System.out.println("请输入qq号:");
String qq = scan.nextLine();
peopleBuilder.setQq(qq);
} else if (2 == otherContact) {
System.out.println("请输入微信号");
String wechat = scan.nextLine();
peopleBuilder.setWechat(wechat);
} else {
System.out.println("无效选择,设置失败!");
}
System.out.println("-------------添加联系⼈成功-------------");
return peopleBuilder.build();
}
}
运行结果:
TestRead.java
package testcode;
import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.FileInputStream;
import java.io.IOException;
public class TestRead {
public static void main(String[] args) throws IOException {
// 从磁盘⽂件⾥读取,并反序列化为 Message 实例
Contacts contacts = Contacts.parseFrom(
new FileInputStream("src/main/java/com/example/start/contacts.bin"));
// 打印
printContacts(contacts);
//System.out.println(contacts.toString());
}
private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {
for (int i = 0; i < contacts.getContactsCount(); i++) {
System.out.println("--------------联系⼈" + (i + 1) + "-----------");
PeopleInfo peopleInfo = contacts.getContacts(i);
System.out.println("姓名: " + peopleInfo.getName());
System.out.println("年龄: " + peopleInfo.getAge());
int j = 1;
for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {
System.out.println("电话" + (j++) + ": " + phone.getNumber() + " (" + phone.getType().name() + ")");
}
if (peopleInfo.hasData() && peopleInfo.getData().is(Address.class)) {
Address address = peopleInfo.getData().unpack(Address.class);
if (!address.getHomeAddress().isEmpty()) {
System.out.println("家庭地址:" + address.getHomeAddress());
}
if (!address.getUnitAddress().isEmpty()) {
System.out.println("单位地址:" + address.getUnitAddress());
}
}
switch (peopleInfo.getOtherContactCase()) {
case QQ:
System.out.println("qq号:" + peopleInfo.getQq());
break;
case WECHAT:
System.out.println("微信号:" + peopleInfo.getWechat());
break;
case OTHERCONTACT_NOT_SET:
break;
}
}
}
}
运行结果:
11.map类型
语法⽀持创建⼀个关联映射字段,也就是可以使⽤map类型去声明字段类型,格式为:
map
要注意的是:
升级通讯录⾄2.4版本
最后,通讯录2.4版本想新增联系⼈的备注信息,我们可以使⽤map类型的字段来存储备注信息。
更新contacts.proto(通讯录2.4),更新内容如下:
contacts.proto
syntax = "proto3";
package start;
option java_multiple_files = true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package = "com.example.start"; // 编译后⽣成⽂件所在的包路径
option java_outer_classname = "ContactsProtos"; // 编译后⽣成的proto包装类的类名
import "google/protobuf/any.proto";
message PeopleInfo{
string name = 1;
int32 age = 2;
message Phone {
string number = 1; // 电话号码
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
PhoneType type = 2; // 类型
}
repeated Phone phone = 3; // 电话
google.protobuf.Any data = 4;
oneof other_contact{
string qq = 5;
string wechat = 6;
}
map remark = 7;
}
message Contacts{
repeated PeopleInfo contacts = 1;
}
message Address{
string home_address = 1;
string unit_address = 2;
}
TestWrite.java
package testcode;
import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.Any;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class TestWrite {
public static void main(String[] args) throws IOException {
Contacts.Builder contactsBuilder = Contacts.newBuilder();
// 读取已存在的contacts
try {
contactsBuilder.mergeFrom(new
FileInputStream("src/main/java/com/example/start/contacts.bin"));
} catch (FileNotFoundException e) {
System.out.println("contacts.bin not found. Creating a new file.");
}
// 新增⼀个联系⼈
contactsBuilder.addContacts(addPeopleInfo());
// 将新的contacts写回磁盘
FileOutputStream output = new FileOutputStream("src/main/java/com/example/start/contacts.bin");
contactsBuilder.build().writeTo(output);
output.close();
}
private static PeopleInfo addPeopleInfo() {
Scanner scan = new Scanner(System.in);
PeopleInfo.Builder peopleBuilder = PeopleInfo.newBuilder();
System.out.println("-------------新增联系⼈-------------");
System.out.print("请输⼊联系⼈姓名: ");
String name = scan.nextLine();
peopleBuilder.setName(name);
System.out.print("请输⼊联系⼈年龄: ");
int age = scan.nextInt();
peopleBuilder.setAge(age);
scan.nextLine();
for (int i = 0; ; i++) {
System.out.print("请输⼊联系⼈电话" + (i + 1) + "(只输⼊回⻋完成电话新 增): ");
String number = scan.nextLine();
if (number.isEmpty()) {
break;
}
PeopleInfo.Phone.Builder phoneBuilder = PeopleInfo.Phone.newBuilder();
phoneBuilder.setNumber(number);
System.out.print("选择此电话类型 (1、移动电话 2、固定电话) : ");
int type = scan.nextInt();
scan.nextLine();
switch (type) {
case 1:
phoneBuilder.setType(PeopleInfo.Phone.PhoneType.MP);
break;
case 2:
phoneBuilder.setType(PeopleInfo.Phone.PhoneType.TEL);
break;
default:
System.out.println("⾮法选择,使⽤默认值!");
break;
}
peopleBuilder.addPhone(phoneBuilder);
}
Address.Builder addressBulider = Address.newBuilder();
System.out.println("请输入联系人家庭地址:");
String homeAddress = scan.nextLine();
addressBulider.setHomeAddress(homeAddress);
System.out.println("请输入联系人单位地址");
String unitAddress = scan.nextLine();
addressBulider.setUnitAddress(unitAddress);
peopleBuilder.setData(Any.pack(addressBulider.build()));
System.out.println("请选择要添加的其他联系方式(1.qq号 2.微信号):");
int otherContact = scan.nextInt();
scan.nextLine();
if (1 == otherContact) {
System.out.println("请输入qq号:");
String qq = scan.nextLine();
peopleBuilder.setQq(qq);
} else if (2 == otherContact) {
System.out.println("请输入微信号");
String wechat = scan.nextLine();
peopleBuilder.setWechat(wechat);
} else {
System.out.println("无效选择,设置失败!");
}
for (int i = 0; ; i++) {
System.out.println("请输入备注:" + (i + 1) + "标题(只输入回车完成备注新增):");
String key = scan.nextLine();
if (key.isEmpty()) {
break;
}
System.out.println("请输入备注内容:");
String value = scan.nextLine();
peopleBuilder.putRemark(key, value);
}
System.out.println("-------------添加联系⼈成功-------------");
return peopleBuilder.build();
}
}
运行结果:
package testcode;
import com.example.start.Address;
import com.example.start.Contacts;
import com.example.start.PeopleInfo;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Map;
public class TestRead {
public static void main(String[] args) throws IOException {
// 从磁盘⽂件⾥读取,并反序列化为 Message 实例
Contacts contacts = Contacts.parseFrom(
new FileInputStream("src/main/java/com/example/start/contacts.bin"));
// 打印
printContacts(contacts);
//System.out.println(contacts.toString());
}
private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {
for (int i = 0; i < contacts.getContactsCount(); i++) {
System.out.println("--------------联系⼈" + (i + 1) + "-----------");
PeopleInfo peopleInfo = contacts.getContacts(i);
System.out.println("姓名: " + peopleInfo.getName());
System.out.println("年龄: " + peopleInfo.getAge());
int j = 1;
for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {
System.out.println("电话" + (j++) + ": " + phone.getNumber() + " (" + phone.getType().name() + ")");
}
if (peopleInfo.hasData() && peopleInfo.getData().is(Address.class)) {
Address address = peopleInfo.getData().unpack(Address.class);
if (!address.getHomeAddress().isEmpty()) {
System.out.println("家庭地址:" + address.getHomeAddress());
}
if (!address.getUnitAddress().isEmpty()) {
System.out.println("单位地址:" + address.getUnitAddress());
}
}
switch (peopleInfo.getOtherContactCase()) {
case QQ:
System.out.println("qq号:" + peopleInfo.getQq());
break;
case WECHAT:
System.out.println("微信号:" + peopleInfo.getWechat());
break;
case OTHERCONTACT_NOT_SET:
break;
}
for (Map.Entry entry : peopleInfo.getRemarkMap().entrySet()) {
System.out.println(" " + entry.getKey() + " : " + entry.getValue());
}
}
}
}
运行结果:
12.默认值
反序列化消息时,如果被反序列化的⼆进制序列中不包含某个字段,反序列化对象中相应字段时,就会设置为该字段的默认值。不同的类型对应的默认值不同:
13.更新消息
更新规则
如果现有的消息类型已经不再满⾜我们的需求,例如需要扩展⼀个字段,在不破坏任何现有代码的情况下更新消息类型⾮常简单。遵循如下规则即可:
移除老字段错误示例:
proto.update.client contacts.proto(移除字段之前)
syntax = "proto3";
package client;
option java_multiple_files = true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package = "com.example.update.client"; // 编译后⽣成⽂件所在的包路径
option java_outer_classname = "ContactsProtos"; // 编译后⽣成的proto包装类的类名
message PeopleInfo{
string name = 1;
int32 age = 2;
message Phone {
string number = 1; // 电话号码
}
repeated Phone phone = 3; // 电话
}
message Contacts{
repeated PeopleInfo contacts = 1;
}
proto.update.service contacts.proto(移除字段age)
(重行编译一下,使用maven插件)
TestWrite.java
package com.example.update.service;
import com.example.update.service.Contacts.Builder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class TestWrite {
public static void main(String[] args) throws IOException {
Builder contactsBuilder = Contacts.newBuilder();
// 读取已存在的contacts
try {
contactsBuilder.mergeFrom(new
FileInputStream("src/main/java/com/example/service/contacts.bin"));
} catch (FileNotFoundException e) {
System.out.println("contacts.bin not found. Creating a new file.");
}
// 新增⼀个联系⼈
contactsBuilder.addContacts(addPeopleInfo());
// 将新的contacts写回磁盘
FileOutputStream output = new FileOutputStream("src/main/java/com/example/update/contacts.bin");
contactsBuilder.build().writeTo(output);
output.close();
}
private static PeopleInfo addPeopleInfo() {
Scanner scan = new Scanner(System.in);
PeopleInfo.Builder peopleBuilder = PeopleInfo.newBuilder();
System.out.println("-------------新增联系⼈-------------");
System.out.print("请输⼊联系⼈姓名: ");
String name = scan.nextLine();
peopleBuilder.setName(name);
// System.out.print("请输⼊联系⼈年龄: ");
// int age = scan.nextInt();
// peopleBuilder.setAge(age);
// scan.nextLine();
System.out.print("请输⼊联系⼈生日: ");
int bir = scan.nextInt();
peopleBuilder.setBirthday(bir);
scan.nextLine();
for (int i = 0; ; i++) {
System.out.print("请输⼊联系⼈电话" + (i + 1) + "(只输⼊回⻋完成电话新 增): ");
String number = scan.nextLine();
if (number.isEmpty()) {
break;
}
PeopleInfo.Phone.Builder phoneBuilder = PeopleInfo.Phone.newBuilder();
phoneBuilder.setNumber(number);
peopleBuilder.addPhone(phoneBuilder);
}
System.out.println("-------------添加联系⼈成功-------------");
return peopleBuilder.build();
}
}
运行结果:
TestRead.java
package com.example.update.client;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.FileInputStream;
import java.io.IOException;
public class TestRead {
public static void main(String[] args) throws IOException {
// 从磁盘⽂件⾥读取,并反序列化为 Message 实例
Contacts contacts = Contacts.parseFrom(
new FileInputStream("src/main/java/com/example/update/contacts.bin"));
// 打印
printContacts(contacts);
}
private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {
for (int i = 0; i < contacts.getContactsCount(); i++) {
System.out.println("--------------联系⼈" + (i + 1) + "-----------");
PeopleInfo peopleInfo = contacts.getContacts(i);
System.out.println("姓名: " + peopleInfo.getName());
System.out.println("年龄: " + peopleInfo.getAge());
int j = 1;
for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {
System.out.println("电话" + (j++) + ": " + phone.getNumber());
}
}
}
}
运行结果:
结论:不能重复使用字段编号,不建议直接删除或注释掉字段。
14.保留字段reserved
如果通过删除或注释掉字段来更新消息类型,未来的⽤⼾在添加新字段时,有可能会使⽤以前已经
存在,但已经被删除或注释掉的字段编号。将来使⽤该.proto的旧版本时的程序会引发很多问题:数据损坏、隐私错误等等。确保不会发⽣这种情况的⼀种⽅法是:使⽤ reserved 将指定字段的编号或名称设置为保留项。当我们再使⽤这些编号或名称时,protocol buffer的编译器将会警告这些编号或名称不可⽤。举个例⼦:
message Message {
// 设置保留项
reserved 100, 101, 200 to 299;
reserved "field3", "field4";
// 注意:不要在⼀⾏ reserved 声明中同时声明字段编号和名称。
// reserved 102, "field5";
// 设置保留项之后,下⾯代码会告警
int32 field1 = 100; //告警:Field 'field1' uses reserved number 100
int32 field2 = 101; //告警:Field 'field2' uses reserved number 101
int32 field3 = 102; //告警:Field name 'field3' is reserved
int32 field4 = 103; //告警:Field name 'field4' is reserved
}
15.未知字段
在通讯录3.0版本中,我们向service⽬录下的contacts.proto新增了‘⽣⽇’字段,但对于client相
关的代码并没有任何改动。验证后发现新代码序列化的消息(service)也可以被旧代码(client)解析。并且这⾥要说的是,新增的‘⽣⽇’字段在旧程序(client)中其实并没有丢失,⽽是会作为旧程序的未知字段。
代码示例:
service.contacts.proto
service.TestWrite.java
package com.example.update.service;
import com.example.update.service.Contacts.Builder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class TestWrite {
public static void main(String[] args) throws IOException {
Builder contactsBuilder = Contacts.newBuilder();
// 读取已存在的contacts
try {
contactsBuilder.mergeFrom(new
FileInputStream("src/main/java/com/example/service/contacts.bin"));
} catch (FileNotFoundException e) {
System.out.println("contacts.bin not found. Creating a new file.");
}
// 新增⼀个联系⼈
contactsBuilder.addContacts(addPeopleInfo());
// 将新的contacts写回磁盘
FileOutputStream output = new FileOutputStream("src/main/java/com/example/update/contacts.bin");
contactsBuilder.build().writeTo(output);
output.close();
}
private static PeopleInfo addPeopleInfo() {
Scanner scan = new Scanner(System.in);
PeopleInfo.Builder peopleBuilder = PeopleInfo.newBuilder();
System.out.println("-------------新增联系⼈-------------");
System.out.print("请输⼊联系⼈姓名: ");
String name = scan.nextLine();
peopleBuilder.setName(name);
// System.out.print("请输⼊联系⼈年龄: ");
// int age = scan.nextInt();
// peopleBuilder.setAge(age);
// scan.nextLine();
System.out.print("请输⼊联系⼈生日: ");
int bir = scan.nextInt();
peopleBuilder.setBirthday(bir);
scan.nextLine();
for (int i = 0; ; i++) {
System.out.print("请输⼊联系⼈电话" + (i + 1) + "(只输⼊回⻋完成电话新 增): ");
String number = scan.nextLine();
if (number.isEmpty()) {
break;
}
PeopleInfo.Phone.Builder phoneBuilder = PeopleInfo.Phone.newBuilder();
phoneBuilder.setNumber(number);
peopleBuilder.addPhone(phoneBuilder);
}
System.out.println("-------------添加联系⼈成功-------------");
return peopleBuilder.build();
}
}
运行结果:
client.TestRead.java
package com.example.update.client;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.FileInputStream;
import java.io.IOException;
public class TestRead {
public static void main(String[] args) throws IOException {
// 从磁盘⽂件⾥读取,并反序列化为 Message 实例
Contacts contacts = Contacts.parseFrom(
new FileInputStream("src/main/java/com/example/update/contacts.bin"));
// 打印
printContacts(contacts);
}
private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {
for (int i = 0; i < contacts.getContactsCount(); i++) {
System.out.println("--------------联系⼈" + (i + 1) + "-----------");
PeopleInfo peopleInfo = contacts.getContacts(i);
System.out.println("姓名: " + peopleInfo.getName());
System.out.println("年龄: " + peopleInfo.getAge());
int j = 1;
for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {
System.out.println("电话" + (j++) + ": " + phone.getNumber());
}
System.out.println("未知字段内容:\n" + peopleInfo.getUnknownFields());
}
}
}
运行结果:
未知字段从哪获取?
在PeopleInfo.java 的PeopleInfo 类中,有个 getUnknownFields() ⽅法⽤来获取未知字段:
public final com.google.protobuf.UnknownFieldSet getUnknownFields() {...}
UnknownFieldSet类介绍
public final class UnknownFieldSet implements MessageLite {
private final TreeMap fields;
public Map asMap() {...}
public boolean hasField(int number) {...}
public Field getField(int number) {...}
// ----------------- 重写了 toString ----------------
public String toString() {...}
// ------------------------builder------------------
public static final class Builder implements MessageLite.Builder {
public Builder clear() {...}
public Builder clearField(int number) {...}
public boolean hasField(int number) {...}
public Builder addField(int number, Field field) {...}
public Map asMap() {...}
}
public static final class Field {
private List varint;
private List fixed32;
private List fixed64;
private List lengthDelimited;
private List group;
public List getVarintList() {...}
public List getFixed32List() {...}
public List getFixed64List() {...}
public List getLengthDelimitedList() {...}
public List getGroupList() {...}
// 省略了 Field Builder : 是⼀些处理字段的⽅法,例如设置、获取、清理
}
}
升级通讯录3.1版本---验证未知字段
更新 TestRead.java (通讯录3.1),在这个版本中,需要打印出未知字段的内容。更新的代码如下:
package com.example.update.client;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.FileInputStream;
import java.io.IOException;
public class TestRead {
public static void main(String[] args) throws IOException {
// 从磁盘⽂件⾥读取,并反序列化为 Message 实例
Contacts contacts = Contacts.parseFrom(
new FileInputStream("src/main/java/com/example/update/contacts.bin"));
// 打印
printContacts(contacts);
}
private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {
for (int i = 0; i < contacts.getContactsCount(); i++) {
System.out.println("--------------联系⼈" + (i + 1) + "-----------");
PeopleInfo peopleInfo = contacts.getContacts(i);
System.out.println("姓名: " + peopleInfo.getName());
System.out.println("年龄: " + peopleInfo.getAge());
int j = 1;
for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {
System.out.println("电话" + (j++) + ": " + phone.getNumber());
}
System.out.println("未知字段内容:\n" + peopleInfo.getUnknownFields());
}
}
}
其他⽂件均不⽤做任何修改,运⾏Client下的main函数可得如下结果:
前后兼容性
根据上述的例⼦可以得出,pb是具有向前兼容的。为了叙述⽅便,把增加了“⽣⽇”属的TestWirte.java称为“新模块”;未做变动的TestRead.java称为“⽼模块”。
16.选项option
.proto⽂件中可以声明许多选项,使⽤option 标注。选项能影响proto编译器的某些处理⽅式。
选项分类:
选项的完整列表在 google/protobuf/descriptor.proto 中定义。部分代码:
syntax = "proto2"; // descriptor.proto 使⽤ proto2 语法版本
message FileOptions { ... } // ⽂件选项 定义在 FileOptions 消息中
message MessageOptions { ... } // 消息类型选项 定义在 MessageOptions 消息中
message FieldOptions { ... } // 消息字段选项 定义在 FieldOptions 消息中
message OneofOptions { ... } // oneof字段选项 定义在 OneofOptions 消息中
message EnumOptions { ... } // 枚举类型选项 定义在 EnumOptions 消息中
message EnumValueOptions { .. } // 枚举值选项 定义在 EnumValueOptions 消息中
message ServiceOptions { ... } // 服务选项 定义在 ServiceOptions 消息中
message MethodOptions { ... } // 服务⽅法选项 定义在 MethodOptions 消息中
由此可⻅,选项分为 ⽂件级、消息级、字段级 等等,但并没有⼀种选项能作⽤于所有的类型。
JAVA常⽤选项列举
java_multiple_files:编译后⽣成的⽂件是否分为多个⽂件,该选项为⽂件选项。
java_package:编译后⽣成⽂件所在的包路径,该选项为⽂件选项。
java_outer_classname:编译后⽣成的proto包装类的类名,该选项为⽂件选项。
allow_alias:允许将相同的常量值分配给不同的枚举常量,⽤来定义别名。该选项为枚举选项。
举个例⼦:
enum PhoneType {
option allow_alias =true;
MP =0;
TEL =1;
LANDLINE =1; // 若不加 option allow_alias = true; 这⼀⾏会编译报错
}
设置⾃定义选项:
https://protobuf.dev/programming-guides/proto2/
17.通讯录4.0实现---⽹络版
需求:
Protobuf还常⽤于通讯协议、服务端数据交换场景。那么在这个⽰例中,我们将实现⼀个⽹络版本的通讯录,模拟实现客⼾端与服务端的交互,通过Protobuf来实现各端之间的协议序列化。
需求如下:
proto.internet.client.contacts.proto
syntax = "proto3";
package client;
option java_multiple_files = true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package = "com.example.internet.client.dto"; // 编译后⽣成⽂件所在的包路径
option java_outer_classname = "ContactsProtos"; // 编译后⽣成的proto包装类的类名
message PeopleInfoRequest {
string name = 1; // 姓名
int32 age = 2; // 年龄
message Phone {
string number = 1; // 电话号码
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
PhoneType type = 2; // 类型
}
repeated Phone phone = 3; // 电话
map remark = 4; // 备注
}
message PeopleInfoResponse {
string uid = 1;
}
proto.internet.service.contacts.proto
syntax = "proto3";
package service;
option java_multiple_files = true; // 编译后⽣成的⽂件是否分为多个⽂件
option java_package = "com.example.internet.service.dto"; // 编译后⽣成⽂件所在的包路径
option java_outer_classname = "ContactsProtos"; // 编译后⽣成的proto包装类的类名
message PeopleInfoRequest {
string name = 1; // 姓名
int32 age = 2; // 年龄
message Phone {
string number = 1; // 电话号码
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
PhoneType type = 2; // 类型
}
repeated Phone phone = 3; // 电话
map remark = 4; // 备注
}
message PeopleInfoResponse {
string uid = 1;
}
com.example.internet.client.BytesUtils.java
package com.example.internet.client;
public class BytesUtils {
/**
* 获取 bytes 有效⻓度
* @param bytes
* @return
*/
public static int getValidLength(byte[] bytes){
int i = 0;
if (null == bytes || 0 == bytes.length)
return i;
for (; i < bytes.length; i++) {
if (bytes[i] == '\0')
break;
}
return i;
}
/**
* 截取 bytes
* @param b
* @param off
* @param length
* @return
*/
public static byte[] subByte(byte[] b,int off,int length){
byte[] b1 = new byte[length];
System.arraycopy(b, off, b1, 0, length);
return b1;
}
}
com.example.internet.client.ContactsClient.java
package com.example.internet.client;
import com.example.internet.client.dto.PeopleInfoRequest;
import com.example.internet.client.dto.PeopleInfoResponse;
import com.example.internet.client.BytesUtils;
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class ContactsClient {
private static final SocketAddress ADDRESS = new InetSocketAddress("localhost", 8888);
public static void main(String[] args) throws IOException {
// 创建客⼾端 DatagramSocket
DatagramSocket socket = new DatagramSocket();
// 构造 request 请求数据
PeopleInfoRequest request = createRequest();
// 序列化 request
byte[] requestData = request.toByteArray();
// 创建 request 数据报
DatagramPacket requestPacket = new DatagramPacket(requestData,
requestData.length, ADDRESS);
// 发送 request 数据报
socket.send(requestPacket);
System.out.println("发送成功!");
// 创建 response 数据报,⽤于接收服务端返回的响应
byte[] udpResponse = new byte[1024];
DatagramPacket responsePacket = new DatagramPacket(udpResponse,
udpResponse.length);
// 接收 response 数据报
socket.receive(responsePacket);
// 获取有效的 response
int length = BytesUtils.getValidLength(udpResponse);
byte[] reqsponseData = BytesUtils.subByte(udpResponse, 0, length);
// 反序列化 response,打印结果
PeopleInfoResponse response = PeopleInfoResponse.parseFrom(reqsponseData);
System.out.printf("接收到服务端返回的响应:%s", response.toString());
}
private static PeopleInfoRequest createRequest() {
System.out.println("------输⼊需要传输的联系⼈信息-----");
Scanner scan = new Scanner(System.in);
PeopleInfoRequest.Builder peopleBuilder = PeopleInfoRequest.newBuilder();
System.out.print("请输⼊联系⼈姓名: ");
String name = scan.nextLine();
peopleBuilder.setName(name);
System.out.print("请输⼊联系⼈年龄: ");
int age = scan.nextInt();
peopleBuilder.setAge(age);
scan.nextLine();
for (int i = 0; ; i++) {
System.out.print("请输⼊联系⼈电话" + (i + 1) + "(只输⼊回⻋完成电话新增): ");
String number = scan.nextLine();
if (number.isEmpty()) {
break;
}
PeopleInfoRequest.Phone.Builder phoneBuilder = PeopleInfoRequest.Phone.newBuilder();
phoneBuilder.setNumber(number);
peopleBuilder.addPhone(phoneBuilder);
}
for (int i = 0; ; i++) {
System.out.print("请输⼊备注" + (i + 1) + "标题 (只输⼊回⻋完成备注新增): ");
String remarkKey = scan.nextLine();
if (remarkKey.isEmpty()) {
break;
}
System.out.print("请输⼊备注" + (i + 1) + "内容: ");
String remarkValue = scan.nextLine();
peopleBuilder.putRemark(remarkKey, remarkValue);
}
System.out.println("------------输⼊结束-----------");
return peopleBuilder.build();
}
}
com.example.internet.service.BytesUtils.java
package com.example.internet.service;
public class BytesUtils {
/**
* 获取 bytes 有效⻓度
*
* @param bytes
* @return
*/
public static int getValidLength(byte[] bytes) {
int i = 0;
if (null == bytes || 0 == bytes.length)
return i;
for (; i < bytes.length; i++) {
if (bytes[i] == '\0')
break;
}
return i;
}
/**
* 截取 bytes
*
* @param b
* @param off
* @param length
* @return
*/
public static byte[] subByte(byte[] b, int off, int length) {
byte[] b1 = new byte[length];
System.arraycopy(b, off, b1, 0, length);
return b1;
}
}
com.example.internet.service.ContactsService.java
package com.example.internet.service;
import com.example.internet.service.dto.PeopleInfoRequest;
import com.example.internet.service.dto.PeopleInfoResponse;
import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ContantsService {
//服务器socket要绑定固定的端⼝
private static final int PORT = 8888;
public static void main(String[] args) throws IOException {
// 创建服务端DatagramSocket,指定端⼝,可以发送及接收UDP数据报
DatagramSocket socket = new DatagramSocket(PORT);
// 不停接收客⼾端udp数据报
while (true){
System.out.println("等待接收UDP数据报...");
// 创建 request 数据报,⽤于接收客⼾端发送的数据
byte[] udpRequest = new byte[1024];
// 1m=1024kb, 1kb=1024byte,
//UDP最多64k(包含UDP⾸部8byte)
DatagramPacket requestPacket = new DatagramPacket(udpRequest, udpRequest.length);
// 接收 request 数据报,在接收到数据报之前会⼀直阻塞,
socket.receive(requestPacket);
// 获取有效的 request
int length = BytesUtils.getValidLength(udpRequest);
byte[] requestData = BytesUtils.subByte(udpRequest, 0, length);
// 反序列化 request
PeopleInfoRequest request = PeopleInfoRequest.parseFrom(requestData);
System.out.println("接收到请求数据:");
System.out.println(request.toString());
// 构造 response
PeopleInfoResponse response = PeopleInfoResponse.newBuilder().setUid("111111111").build();
// 序列化 response
byte[] responseData = response.toByteArray();
// 构造 response 数据报,注意接收的客⼾端数据报包含IP和端⼝号,要设置到响应
//的数据报中
DatagramPacket responsePacket = new DatagramPacket(responseData, responseData.length, requestPacket.getSocketAddress());
// 发送 response 数据报
socket.send(responsePacket);
}
}
}
运行结果:
19.序列化能⼒对⽐验证
在这⾥让我们分别使⽤PB与JSON的序列化与反序列化能⼒,对值完全相同的⼀份结构化数据进⾏不同次数的性能测试。为了可读性,下⾯这⼀份⽂本使⽤JSON格式展⽰了需要被进⾏测试的结构化数据内容:
20.总结:
总结: