一、环境的安装及测试
- 首先需要获取安装包
- 接着逐条输入以下指令即可
tar -xzf protobuf-2.1.0.tar.gz
cd protobuf-2.1.0
./configure --prefix=/usr/local/protobuf
make
make check
make install
./configure --prefix=/usr/local/protobuf 用于配置安装的路径
- 2 > sudo vim /etc/profile
添加
export PATH=$PATH:/usr/local/protobuf/bin/
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
保存执行
source /etc/profile
- 3 > sudo vim ~/.profile
添加
export PATH=$PATH:/usr/local/protobuf/bin/
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
保存执行
source ~/.profile
- 4 > 配置动态链接库路径
sudo vim /etc/ld.so.conf
插入:
/usr/local/protobuf/lib
- 5 > su #root 权限
ldconfig
-
查看是否安装成功
protoc --version
-
6 > 写消息文件:msg.proto
package lm;
message helloworld
{
required int32 id = 1; // ID
required string str = 2; // str
optional int32 opt = 3; //optional field
}
- 将消息文件msg.proto映射成cpp文件
protoc -I=. --cpp_out=. msg.proto
可以看到生成了
msg.pb.h 和msg.pb.cc
--objc_out
- 7 > 写序列化消息的进程
#include "msg.pb.h"
#include <fstream>
#include <iostream>
using namespace std;
int main(void)
{
lm::helloworld msg1;
msg1.set_id(101);
msg1.set_str("hello");
fstream output("./log", ios::out | ios::trunc | ios::binary);
if (!msg1.SerializeToOstream(&output)) {
cerr << "Failed to write msg." << endl;
return -1;
}
return 0;
}
- 编译 write.cc
g++ msg.pb.cc write.cc -o write `pkg-config --cflags --libs protobuf` -lpthread
-
执行write
./write, 可以看到生成了log文件 -
7> 写反序列化的进程
reader.cc
#include "msg.pb.h"
#include <fstream>
#include <iostream>
using namespace std;
void ListMsg(const lm::helloworld & msg) {
cout << msg.id() << endl;
cout << msg.str() << endl;
}
int main(int argc, char* argv[]) {
lm::helloworld msg1;
{
fstream input("./log", ios::in | ios::binary);
if (!msg1.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
}
ListMsg(msg1);
}
-
编译:g++ msg.pb.cc reader.cc -o reader
pkg-config --cflags --libs protobuf
-lpthread
执行./reader 输出 :
101
hello -
编写makefile
all: write reader
clean:
rm -f write reader msg.*.cc msg.*.h *.o log
proto_msg:
protoc --cpp_out=. msg.proto
write: msg.pb.cc write.cc
g++ msg.pb.cc write.cc -o write `pkg-config --cflags --libs protobuf`
reader: msg.pb.cc reader.cc
g++ msg.pb.cc reader.cc -o reader `pkg-config --cflags --libs protobuf`
二、序列化分析
例子一
080a 120a 6865 6c6c 6f2c 6c6f 6e6e …hello,lonn
序列化之后,每个字段之前都有一个字段的标识,如上面标成黄色的 08 ,12。
这些标识,有两个含义一个是字段序号,另一个是字段的种类。
例子二
在多一个例子
080a 1002 1803 2004 2a0a 6865 6c6c 6f2c 6c6f 6e6e
分析
由上面的两个例子,我们得到了两个序列化的数据
080a 120a 6865 6c6c 6f2c 6c6f 6e6e …hello,lonn
080a 1002 1803 2004 2a0a 6865 6c6c 6f2c 6c6f 6e6e
上面的黄色的数组都是用来表示一个定义好的字段的。
其中包含了field number(简写成fn)以及wire type(简写为wt)。
其中field number是proto文件中标注的该字段数字代号,而wire type表示本字段的数据归类。
数据归类主要用于提醒反序列化程序如何判断本字段值占据几个字节。
Wire Type | 解释 | 数据类型 |
---|---|---|
0 | varint变长整型(见下文) | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 固定8字节 | fixed64, sfixed64, double |
2 | 需显式告知长度(见下文) | string, bytes, 嵌套类型(embedded messages),repeated字段 |
3 | (已废弃) | (已废弃) |
4 | (已废弃) | (已废弃) |
5 | 固定4字节 | fixed32, sfixed32, float |
因为wire type种类很少,为了节约字节,write type只用了3个bits来表示,而fn则使用更高位来表示。
080a 120a 6865 6c6c 6f2c 6c6f 6e6e …hello,lonn
080a 1002 1803 2004 2a0a 6865 6c6c 6f2c 6c6f 6e6e
如上面的序列化后的数据的 第一个数字标识 08。十六进制08
等于二进制的0000 1000
。
-
二进制的 第1位 最高字节。
每个字节最高位不用来表示具体数值,只用来表示“本字节是不是这个数字的最后一个字节”。0表示最后一个字节,1表示不是最后一字节、后面还有。 -
二进制的 第2~5位 是field number
field number 的数字是根据proto标注的字段数据决定的”。这里是1。
注意:field number大于15时会使用更多的位来实现表示,如下图:
-
二进制的 后三位,是wire type
wire type固定在后三位。这里是0,表示varint变长整型。