Protocol Buffer 簡介
Protocol Buffer是Google開源的一種在不同編程語言之間通信的技術(shù)巫延。它的核心思想就是收奔,通過一個.proto文件蹭劈,定義好消息格式,再用不同語言相關(guān)的工具(官方默認提供Java鳖谈、C++凝危、Python波俄、Go),將.proto指定的消息格式轉(zhuǎn)化成語言相關(guān)的源代碼媒抠。
Java中的使用舉例
官網(wǎng)上有現(xiàn)成的Java的例子弟断,基本上非常簡單,按步驟來就可以趴生,主要就是三板斧:
1.定義.proto文件阀趴,說明消息的格式
2.使用protocol buffer compiler將.proto文件轉(zhuǎn)化成.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;
}
關(guān)鍵字說明:
- package:proto文件的組織也是有層次關(guān)系的,這個層次關(guān)系不一定和Java包的層次關(guān)系是對應的刘急。例如在一個proto文件可能用到另一個proto文件中定義的消息結(jié)構(gòu)棚菊,那么可以用import來實現(xiàn)。
- java_package:產(chǎn)生的類的包名叔汁。
- java_outer_classname:產(chǎn)生的類的類名统求。注意我們在代碼中使用Java Protocol Buffer API的時候,主要用到的是.proto文件里面中的message所指定的類据块,例如上面的Person類码邻,而一般不會直接用到AddressBookProtos類,AddressBookProtos類只是一個裝載這些消息的類而已另假。
- required:必須提供此值像屋,否則 message 會被認為是未初始化的。
- optional:可以設置也可以不設置此值边篮。如果未設置 optional field 將使用默認值己莺。
- repeated:可以理解為動態(tài)長度的數(shù)組。
- Tag: =1 =2 用于指定 field 的唯一的 tag戈轿,在使用于二進制編碼中凌受。在 Protocol Buffers 編碼時,Tag 值為 1 ~ 15 會比值為 15 以上的少消耗 1 個字節(jié)的空間思杯。
生成Java類
編譯工具需要單獨下載胜蛉。
安裝好編譯工具后,使用以下的命令就可以生成java相關(guān)的類了智蝠。
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto
SRC_DIR指定了原代碼的目錄腾么,DST_DIR指定了生成的類的目錄奈梳。另外當然還要加上咱們的.proto文件杈湾。
為了正常使用生成的Java類,還要下載Protocol Buffer相關(guān)的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("jdoe@example.com")
.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
一個生成二進制數(shù)據(jù)的例子宵蛀,pack_demo.c
#include <stdio.h>
#include <stdlib.h>
#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 = "jiangjf@sugon.com";
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 <stdio.h>
#include <stdlib.h>
#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;
}
執(zhí)行./pack_demo | ./unpack_demo,可以看到生成的二進制被解析的結(jié)果:
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