Protocol Buffer 简介
Protocol Buffer是Google开源的一种在不同编程语言之间通信的技术。它的核心思想就是,通过一个.proto文件,定义好消息格式,再用不同语言相关的工具(官方默认提供Java、C++、Python、Go),将.proto指定的消息格式转化成语言相关的源代码。
Java中的使用举例
官网上有现成的Java的例子,基本上非常简单,按步骤来就可以,主要就是三板斧:
1.定义.proto文件,说明消息的格式
2.使用protocol buffer compiler将.proto文件转化成.java文件
3.使用Java protocol buffer API来生成或者解析消息。
定义proto文件
package tutorial;
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
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;
}
message AddressBook {
repeated Person person = 1;
}
关键字说明:
- package:proto文件的组织也是有层次关系的,这个层次关系不一定和Java包的层次关系是对应的。例如在一个proto文件可能用到另一个proto文件中定义的消息结构,那么可以用import来实现。
- java_package:产生的类的包名。
- java_outer_classname:产生的类的类名。注意我们在代码中使用Java Protocol Buffer API的时候,主要用到的是.proto文件里面中的message所指定的类,例如上面的Person类,而一般不会直接用到AddressBookProtos类,AddressBookProtos类只是一个装载这些消息的类而已。
- required:必须提供此值,否则 message 会被认为是未初始化的。
- optional:可以设置也可以不设置此值。如果未设置 optional field 将使用默认值。
- repeated:可以理解为动态长度的数组。
- Tag: =1 =2 用于指定 field 的唯一的 tag,在使用于二进制编码中。在 Protocol Buffers 编码时,Tag 值为 1 ~ 15 会比值为 15 以上的少消耗 1 个字节的空间。
生成Java类
编译工具需要单独下载。
安装好编译工具后,使用以下的命令就可以生成java相关的类了。
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto
SRC_DIR指定了原代码的目录,DST_DIR指定了生成的类的目录。另外当然还要加上咱们的.proto文件。
为了正常使用生成的Java类,还要下载Protocol Buffer相关的Java库,http://central.maven.org/maven2/com/google/protobuf/protobuf-java/2.6.1/protobuf-java-2.6.1.jar。
生成和解析消息
每个生成的解析类都包含了用来生成和解析二进制消息的方法,包括:
byte[] toByteArray();: serializes the message and returns a byte array containing its raw bytes.
static Person parseFrom(byte[] data);: parses a message from the given byte array.
void writeTo(OutputStream output);: serializes the message and writes it to an OutputStream.
static Person parseFrom(InputStream input);: reads and parses a message from an InputStream.
生成消息:
Person john =
Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("[email protected]")
.addPhone(
Person.PhoneNumber.newBuilder()
.setNumber("555-4321")
.setType(Person.PhoneType.HOME))
.build();
解析:
import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.Person;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
class ListPeople {
// Iterates though all people in the AddressBook and prints info about them.
static void Print(AddressBook addressBook) {
for (Person person: addressBook.getPersonList()) {
System.out.println("Person ID: " + person.getId());
System.out.println(" Name: " + person.getName());
if (person.hasEmail()) {
System.out.println(" E-mail address: " + person.getEmail());
}
for (Person.PhoneNumber phoneNumber : person.getPhoneList()) {
switch (phoneNumber.getType()) {
case MOBILE:
System.out.print(" Mobile phone #: ");
break;
case HOME:
System.out.print(" Home phone #: ");
break;
case WORK:
System.out.print(" Work phone #: ");
break;
}
System.out.println(phoneNumber.getNumber());
}
}
}
// Main function: Reads the entire address book from a file and prints all
// the information inside.
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: ListPeople ADDRESS_BOOK_FILE");
System.exit(-1);
}
// Read the existing address book.
AddressBook addressBook =
AddressBook.parseFrom(new FileInputStream(args[0]));
Print(addressBook);
}
}
C中的使用举例
使用protoc-c之前,需要安装,安装的方法见这里。
首先需要定义一个proto文件,这个步骤就不再具体说明,我们以addressbook.proto为例说明。有了proto文件后,使用protoc-c生成.h和.c文件,例如:
protoc-c --c_out=. addressbook.proto
生成的文件如下:
addressbook.pb-c.c
addressbook.pb-c.h
一个生成二进制数据的例子,pack_demo.c
#include
#include
#include "addressbook.pb-c.h"
int main (int argc, const char * argv[])
{
int person_number = 1;
int phone_number_size = 1;
int len = 0;
void *buf = NULL;
Tutorial__Person__PhoneNumber **phone_number_array = NULL;
Tutorial__Person **person_array = NULL;
Tutorial__AddressBook address_book = TUTORIAL__ADDRESS_BOOK__INIT; // AMessage
Tutorial__Person person = TUTORIAL__PERSON__INIT;
Tutorial__Person__PhoneType phone_type = TUTORIAL__PERSON__PHONE_TYPE__HOME;
Tutorial__Person__PhoneNumber phone_number = TUTORIAL__PERSON__PHONE_NUMBER__INIT;
phone_number.number = "13401167356";
phone_number.type= TUTORIAL__PERSON__PHONE_TYPE__MOBILE;
person.name = "JiangJiafu";
person.id = 1;
person.email = "[email protected]";
phone_number_array = malloc(sizeof(Tutorial__Person__PhoneNumber *) * phone_number_size);
person.n_phone = phone_number_size;
person.phone = phone_number_array;
person.phone[0] = &phone_number;
person_array = malloc(sizeof(Tutorial__Person *) * person_number);
if (!person_array)
{
exit(-1);
}
address_book.n_person = phone_number_size;
address_book.person = person_array;
address_book.person[0] = &person;
len = tutorial__address_book__get_packed_size(&address_book);
buf = malloc(len);
tutorial__address_book__pack(&address_book, buf);
fprintf(stderr,"Writing %d serialized bytes\n",len); // See the length of message
fwrite(buf,len,1,stdout); // Write to stdout to allow direct command line piping
free(person_array);
free(phone_number_array);
free(buf); // Free the allocated serialized buffer
return 0;
}
解析二进制的例子(unpack.c):
#include
#include
#include "addressbook.pb-c.h"
#define MAX_MSG_SIZE 1024
static size_t
read_buffer (unsigned max_length, uint8_t *out)
{
size_t cur_len = 0;
size_t nread;
while ((nread=fread(out + cur_len, 1, max_length - cur_len, stdin)) != 0)
{
cur_len += nread;
if (cur_len == max_length)
{
fprintf(stderr, "max message length exceeded\n");
exit(1);
}
}
return cur_len;
}
int main (int argc, const char * argv[])
{
int i = 0;
Tutorial__AddressBook *address_book = NULL;
// Read packed message from standard-input.
uint8_t buf[MAX_MSG_SIZE];
size_t msg_len = read_buffer (MAX_MSG_SIZE, buf);
// Unpack the message using protobuf-c.
address_book = tutorial__address_book__unpack(NULL, msg_len, buf);
if (address_book == NULL)
{
fprintf(stderr, "error unpacking incoming message\n");
exit(1);
}
// display the message's fields.
printf("Received: person number=%d\n", address_book->n_person); // required field
for (i = 0; i < address_book->n_person; ++i)
{
printf("Person name: %s\n", address_book->person[i]->name);
printf("Person id: %d\n", address_book->person[i]->id);
}
// Free the unpacked message
tutorial__address_book__free_unpacked(address_book, NULL);
return 0;
}
执行./pack_demo | ./unpack_demo,可以看到生成的二进制被解析的结果:
Writing 50 serialized bytes
Received: person number=1
Person name: JiangJiafu
Person id: 1
Makefile:
INC_DIR = -I/usr/local/include
CFLAGS = -I.
LDFLAGS = -L/usr/local/lib -lprotobuf-c
all: pack_demo unpack_demo
pack_demo: pack_demo.c addressbook.pb-c.c
gcc $(INC_DIR) $(CFLAGS) pack_demo.c addressbook.pb-c.c -o pack_demo -lm $(LDFLAGS)
unpack_demo: unpack_demo.c addressbook.pb-c.c
gcc $(INC_DIR) $(CFLAGS) unpack_demo.c addressbook.pb-c.c -o unpack_demo -lm $(LDFLAGS)
.PHONY: all clean
clean:
rm -rf pack_demo