最近公司想要做一套自己的IM Server,老大让我学习一下Protocol Buffer。Protocol Buffer是什么东西、做什么用的、有什么优势这里不做解释。
参考:
https://developers.google.com/protocol-buffers/docs/javatutorial
https://developers.google.com/protocol-buffers/docs/proto
http://blog.csdn.net/cchd0001/article/details/50669079
参考官网例子,想要完成此项工作主要分三步:
1.在.proto 中定义消息格式;
2.使用protocol buffer编译程序;
3.使用Java protocol buffer API读写消息;
一、定义消息协议格式(通讯录)
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;
}
喏。上面就是消息格式,格式有点像C和java。把它保存在addressbook.proto文件中。
现在对.proto文件做一个简单说明:
1.“package tutorial”是文件的开始,它是为了防止不同项目之间的命名冲突。如果我们没有声明“java_package”属性,那它就会成为java程序的包名(感兴趣的同学自己试一下),但如果声明了“java_package”是不是就不用“package”了呢,答案是否定的,因为协议文件很可能在其它语言中使用。
2.“java_package”上面介绍过就是生成的.java文件中的包名。
3.“java_outer_classname”表示生成的.java文件名,如果没有声明它那么它会根据.proto文件名生成,例如“my_proto.proto”文件编译后生成的就是“MyProto.java”。
4.消息类型,简单的消息类型包括:bool, int32, float, double,string等等。上面的例子中你还可以看到一个内部类型“PhoneNumber”和一个枚举类型“PhoneType”,其中在“PhoneNumber”中引用了“PhoneType”类型。
5."=1"、"=2"是消息标签,消息中的每一个字段都被定义了一个独一无二的数字标签。这个标签是用来在二进制的消息格式中区分字段的,一旦你的消息开始被使用,这些标签就不应该在被修改了。注意 1 到 15 标签在编码的时候仅占用1 byte ,16 - 2047 占用 2 byte 。因此你应该将 1 - 15 标签保留给最经常被使用的消息元素。另外为未来可能添加的常用元素预留位子。
你能定义的最小的标签是1, 最大是 2的29次方 -1 , 另外 19000 到 19999 也不能用。他们是protobuf 的编译预留标签。
6.
required 格式正确的消息必须有一个这个字段。
optional 格式正确的消息可以有一个或者零个这样的消息。
repeated 这个字段可以有任意多个。字段值的顺序被保留。
二、编译协议文件
1.下载编译器,去github下载,我下载的是window版 https://github.com/google/protobuf/releases/tag/v3.0.0
2.解压文件,bin文件夹里面有一个protoc.exe执行文件。
3.protoc-3.0.0-win32文件夹下新建两个文件夹java-作为文件生成路径;proto-作为协议路径
4.dos中执行:protoc -I=../proto --java_out=../java ../proto/addressbook.proto
5.查看java文件夹,文件已经生成
三、使用Java protocol buffer API读写消息
1.新建maven java工程,pom信息如下:
<span style="white-space:pre"> </span><dependency>
<span style="white-space:pre"> </span><groupId>com.google.protobuf</groupId>
<span style="white-space:pre"> </span><artifactId>protobuf-java</artifactId>
<span style="white-space:pre"> </span><version>3.0.0</version>
<span style="white-space:pre"> </span></dependency>
2.拷贝生成的java文件到工程中
3.新建一个测试类(写)
package com.example.tutorial;
import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.Person;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintStream;
class AddPerson {
// This function fills in a Person message based on user input.
static Person PromptForAddress(BufferedReader stdin,
PrintStream stdout) throws IOException {
Person.Builder person = Person.newBuilder();
stdout.print("Enter person ID: ");
person.setId(Integer.valueOf(stdin.readLine()));
stdout.print("Enter name: ");
person.setName(stdin.readLine());
stdout.print("Enter email address (blank for none): ");
String email = stdin.readLine();
if (email.length() > 0) {
person.setEmail(email);
}
while (true) {
stdout.print("Enter a phone number (or leave blank to finish): ");
String number = stdin.readLine();
if (number.length() == 0) {
break;
}
Person.PhoneNumber.Builder phoneNumber =
Person.PhoneNumber.newBuilder().setNumber(number);
stdout.print("Is this a mobile, home, or work phone? ");
String type = stdin.readLine();
if (type.equals("mobile")) {
phoneNumber.setType(Person.PhoneType.MOBILE);
} else if (type.equals("home")) {
phoneNumber.setType(Person.PhoneType.HOME);
} else if (type.equals("work")) {
phoneNumber.setType(Person.PhoneType.WORK);
} else {
stdout.println("Unknown phone type. Using default.");
}
person.addPhone(phoneNumber);
}
return person.build();
}
// 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 {
if (args.length != 1) {
System.err.println("Usage: AddPerson ADDRESS_BOOK_FILE");
System.exit(-1);
}
AddressBook.Builder addressBook = AddressBook.newBuilder();
// Read the existing address book.
try {
addressBook.mergeFrom(new FileInputStream(args[0]));
} catch (FileNotFoundException e) {
System.out.println(args[0] + ": File not found. Creating a new file.");
}
// Add an address.
addressBook.addPerson(
PromptForAddress(new BufferedReader(new InputStreamReader(System.in)),
System.out));
// Write the new address book back to disk.
FileOutputStream output = new FileOutputStream(args[0]);
addressBook.build().writeTo(output);
output.close();
}
}
测试类做的是新建一个文件来保存序列化的AddressBook信息,当然新建AddressBook需要输入保存的文件地址及person内容。
eclipse中 Run->Run Configurations->Arguments 填写C:\Users\Administrator\Desktop\AddressBook
运行...
输入一个person laowang、一个person laozhang,可见桌面上生成了一个AddressBook文件
C:\Users\Administrator\Desktop\AddressBook: File not found. Creating a new file.
Enter person ID: 1
Enter name: laowang
Enter email address (blank for none): laowang@163.com
Enter a phone number (or leave blank to finish):
...
4.新建一个测试类(读)
package com.example.tutorial;
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);
}
}
运行时输入参数(文件保存路径),可以看到打印的信息:
Person ID: 1
Name: laowang
E-mail address: laowang@163.com
Mobile phone #: 15811111111
Person ID: 2
Name: laozhang
E-mail address: laozhang@163.com
Mobile phone #: 15822222222
好啦,入门级的测试完成了,现在回头看看给我们生成的AddressBookProtos.java中都提供了哪些函数。
标准方法:
一.每个Message和Builder中包含以下方法
1)isInitialized(): 检查是否所有的必须属性已经被设置.
2)toString(): 你懂得.
3)mergeFrom(Message other): (只在builder中) 覆盖已有的单属性,合并重复属性.
4)clear(): (只在builder中) 清空所有的属性值到空状态.
二.序列化和反序列化(这个看方法名就知道了)
1)byte[] toByteArray();: serializes the message and returns a byte array containing its raw bytes.
2)static Person parseFrom(byte[] data);: parses a message from the given byte array.
3)void writeTo(OutputStream output);: serializes the message and writes it to an OutputStream.
4)static Person parseFrom(InputStream input);: reads and parses a message from an InputStream.