Protobuf数据方法- 学习笔记
网络通信和通用数据交换等应用场景中经常使用的技术是 JSON 或 XML,不过现在Google 的protobuf也使用的很频繁;
protobuf 其在效率、兼容性等方面非常出色;
1.protobuf的定义与描述
1.1定义与描述
protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。
你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。
序列化:将结构数据或对象转换成能够被存储和传输(例如网络传输)的格式**,同时应当要保证这个序列化结果在之后(可能在另一个计算环境中)能够被重建回原来的结构数据或对象。
2. 使用protobuf
proto2与proto3的语法上已有较大不同
2.1 定义.proto文件
// 例1: 在 xxx.proto 文件中定义 Example1 message
message Example1 {
optional string stringVal = 1;
optional bytes bytesVal = 2;
message EmbeddedMessage {
int32 int32Val = 1;
string stringVal = 2;
}
optional EmbeddedMessage embeddedExample1 = 3;
repeated int32 repeatedInt32Val = 4;
repeated string repeatedStringVal = 5;
}
我们在上例中定义了一个名为 Example1 的 消息,语法很简单,message 关键字后跟上消息名称:
message xxx {
}
之后我们在其中定义了 message 具有的字段,形式为:
message xxx {
// 字段规则:required -> 字段只能也必须出现 1 次,proto3默认,编写的时候不需要增加字段规则
// 字段规则:optional -> 字段可出现 0 次或1次
// 字段规则:repeated -> 字段可出现任意多次(包括 0)
// 类型:int32、int64、sint32、sint64、string、32-bit ....
// 字段编号:0 ~ 536870911(除去 19000 到 19999 之间的数字)
字段规则 类型 名称 = 字段编号;
}
message Person {
required string name = 1;// 人必须是有名字的且只有一个(身份证上)
required int32 id = 2;// 身份的识别id也必须是唯一的且只有一个
optional string email = 3;// 而人是可以有邮箱或者没有邮箱的,本次定义了最多只有一个邮箱
}
在上例中,我们定义了:
-
类型 string,名为 stringVal 的 optional 可选字段,字段编号为 1,此字段可出现 0 或 1 次
-
类型 bytes,名为 bytesVal 的 optional 可选字段,字段编号为 2,此字段可出现 0 或 1 次
-
类型 EmbeddedMessage(自定义的内嵌 message 类型),名为 embeddedExample1 的 optional 可选字段,字段编号为 3,此字段可出现 0 或 1 次
-
类型 int32,名为 repeatedInt32Val 的 repeated 可重复字段,字段编号为 4,此字段可出现 任意多次(包括 0)
-
类型 string,名为 repeatedStringVal 的 repeated 可重复字段,字段编号为 5,此字段可出现 任意多次(包括 0
2.2 protoc 编译
使用编译工具:https://github.com/protocolbuffers/protobuf/releases
将.proto文件编译成可读写的接口;
示例:创建地址簿应用程序
addressbook.proto文件:
syntax = "proto3";// 定义为proto3,则可以不需要增加字段规则
message Person {
string name = 1;
int32 id = 2;
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
2.2.1 python接口
- 将.proto转换成.py文件
D:\02_tmp\protoc-3.14.0-win64\bin>protoc --python_out=. addressbook.proto
- 生成的py文件:addressbook_pb2.py,内容稍微过下,可略~
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: addressbook.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='addressbook.proto',
package='',
syntax='proto3',
serialized_options=None,
create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\x11\x61\x64\x64ressbook.proto\"\xc3\x01\n\x06Person\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\x05\x12\r\n\x05\x65mail\x18\x03 \x01(\t\x12#\n\x06phones\x18\x04 \x03(\x0b\x32\x13.Person.PhoneNumber\x1a>\n\x0bPhoneNumber\x12\x0e\n\x06number\x18\x01 \x01(\t\x12\x1f\n\x04type\x18\x02 \x01(\x0e\x32\x11.Person.PhoneType\"+\n\tPhoneType\x12\n\n\x06MOBILE\x10\x00\x12\x08\n\x04HOME\x10\x01\x12\x08\n\x04WORK\x10\x02\"&\n\x0b\x41\x64\x64ressBook\x12\x17\n\x06people\x18\x01 \x03(\x0b\x32\x07.Personb\x06proto3'
)
_PERSON_PHONETYPE = _descriptor.EnumDescriptor(
name='PhoneType',
full_name='Person.PhoneType',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='MOBILE', index=0, number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='HOME', index=1, number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='WORK', index=2, number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
],
containing_type=None,
serialized_options=None,
serialized_start=174,
serialized_end=217,
)
_sym_db.RegisterEnumDescriptor(_PERSON_PHONETYPE)
_PERSON_PHONENUMBER = _descriptor.Descriptor(
name='PhoneNumber',
full_name='Person.PhoneNumber',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='number', full_name='Person.PhoneNumber.number', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='type', full_name='Person.PhoneNumber.type', index=1,
number=2, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=110,
serialized_end=172,
)
_PERSON = _descriptor.Descriptor(
name='Person',
full_name='Person',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='name', full_name='Person.name', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='id', full_name='Person.id', index=1,
number=2, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='email', full_name='Person.email', index=2,
number=3, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='phones', full_name='Person.phones', index=3,
number=4, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[_PERSON_PHONENUMBER, ],
enum_types=[
_PERSON_PHONETYPE,
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=22,
serialized_end=217,
)
_ADDRESSBOOK = _descriptor.Descriptor(
name='AddressBook',
full_name='AddressBook',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='people', full_name='AddressBook.people', index=0,
number=1, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=219,
serialized_end=257,
)
_PERSON_PHONENUMBER.fields_by_name['type'].enum_type = _PERSON_PHONETYPE
_PERSON_PHONENUMBER.containing_type = _PERSON
_PERSON.fields_by_name['phones'].message_type = _PERSON_PHONENUMBER
_PERSON_PHONETYPE.containing_type = _PERSON
_ADDRESSBOOK.fields_by_name['people'].message_type = _PERSON
DESCRIPTOR.message_types_by_name['Person'] = _PERSON
DESCRIPTOR.message_types_by_name['AddressBook'] = _ADDRESSBOOK
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
Person = _reflection.GeneratedProtocolMessageType('Person', (_message.Message,), {
'PhoneNumber' : _reflection.GeneratedProtocolMessageType('PhoneNumber', (_message.Message,), {
'DESCRIPTOR' : _PERSON_PHONENUMBER,
'__module__' : 'addressbook_pb2'
# @@protoc_insertion_point(class_scope:Person.PhoneNumber)
})
,
'DESCRIPTOR' : _PERSON,
'__module__' : 'addressbook_pb2'
# @@protoc_insertion_point(class_scope:Person)
})
_sym_db.RegisterMessage(Person)
_sym_db.RegisterMessage(Person.PhoneNumber)
AddressBook = _reflection.GeneratedProtocolMessageType('AddressBook', (_message.Message,), {
'DESCRIPTOR' : _ADDRESSBOOK,
'__module__' : 'addressbook_pb2'
# @@protoc_insertion_point(class_scope:AddressBook)
})
_sym_db.RegisterMessage(AddressBook)
# @@protoc_insertion_point(module_scope)
- 创建项目工程,安装protobuf库:pip install protobuf
- 编写代码,将该.proto序列化写入文件
# -*- coding: utf-8 -*-
from md_demo import addressbook_pb2
# 初始化对象示例
def PromptForAddress(person):
person.id = 1
person.name = "white box"
person.email = "qa_center@diandian.com"
phone_number = person.phones.add()
phone_number.number = "12345678900"
phone_number.type = addressbook_pb2.Person.MOBILE
def write_txt():
address_book = addressbook_pb2.AddressBook()
address_book_file = './data/wirte.txt'
f = open(address_book_file, "wb")
PromptForAddress(address_book.people.add())
f.write(address_book.SerializeToString())
f.close()
def read_txt():
address_book = addressbook_pb2.AddressBook()
address_book_file = './data/wirte.txt'
f = open(address_book_file, "rb")
address_book.ParseFromString(f.read())
f.close()
print(address_book)
2.2.1.1 拓展
一般protobuf不会单独使用,会与网络通信协议结合使用;类似于json与http,比如websocket+protobuf,http+protobuf;
所以如果说针对基于http2协议+protobuf的接口,使用以上的方案,外加locust 或者 nose 或者testng等去实现;
2.2.2 java接口
- addressbook.proto文件
syntax = "proto3";
package tutorial;
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
message Person {
string name = 1;
int32 id = 2;
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
- 将.proto转换成.java文件
D:\02_tmp\protoc-3.14.0-win64\bin>protoc --java_out=. addressbook.proto
- 生成的java文件:AddressBookProtos.java,内容略~
- 创建maven项目,pom.xml如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ProtobufDemo</groupId>
<artifactId>ProtobufDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>ProtobufDemo</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.14.0</version>
</dependency>
</dependencies>
</project>
- 生成的java文件放入工程
- 编写代码
package ProtobufDemo.ProtobufDemo;
import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.Person;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
class AddPerson {
// This function fills in a Person message based on user input.
static Person PromptForAddress() throws IOException {
// 创建Builder
Person.Builder person = Person.newBuilder();
// 设置Person的属性
person.setId(1);
person.setName("Centruy Game");
person.setEmail("qa_center@diandian.com");
Person.PhoneNumber.Builder phoneNumber =
Person.PhoneNumber.newBuilder().setNumber("12345678901");
phoneNumber.setType(Person.PhoneType.WORK);
person.addPhones(phoneNumber);
// 创建Person
return person.build();
};
// 序列化写入txt文件
public static void write_txt() throws IOException {
// 设置person属性
Person person = PromptForAddress();
// 创建addressBook
AddressBook.Builder addressBook = AddressBook.newBuilder();
addressBook.addPeople(person);
// 序列化
byte[] data = addressBook.build().toByteArray();
FileOutputStream fos = new FileOutputStream("protobuf.txt");
fos.write(data);
fos.flush();
fos.close();
}
public static void read_txt() throws FileNotFoundException {
FileInputStream fos = new FileInputStream("protobuf.txt");
try {
AddressBook address = AddressBook.parseFrom(fos);
System.out.println("address="+address.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// Main function: Reads the entire address book from a file,
// adds one person based on user input, then writes it back out to the same
// file.
public static void main(String[] args) throws Exception {
read_txt();
}
}
-
执行结果
-
生成的文件
-
文件内容
-
读取的结果
-
2.2.2.1 拓展
- java接口重点关注:
// 创建Builder
Person.Builder person = Person.newBuilder();
// 创建Person
person.build();
// 序列化
byte[] data = addressBook.build().toByteArray();
// 解析序列化
AddressBook address = AddressBook.parseFrom(fos);
- 作为测试,在执行以http+protobuf为数据结构的接口时,可以结合jmeter或者testng或者Junit去执行。
2.2.3 lua接口
官方没有对接lua的接口,不过有其他第三方的,可以参考:https://github.com/sean-lin/protoc-gen-lua
2.2.4 go接口
待补充