Window+Protobuf使用说明
介绍
起因
由于项目中要用到二进制存储数据,之前使用的方式是按照字节数依次将数据写入字节流中, 但是这样做起来做文件的协议兼容比较难做,所以我们考虑使用 protobuf 来做格式定义, 便于不同版本的文件兼容, 这边使用用起来十分方便且后续添加参数之后, 版本之间的改动很好处理,多出来的参数或者未找到的参数可以使用默认处理即可,十分方便
常用配置文件介绍
程序的配置文件是一个很常用的手段, 每次读取配置文件的信息, 容纳后根据参数决定我们的执行顺序, 是程序的一个很好的设计方式, 常用的配置文件我们会选择使用自己可读的文件格式,进行一定的程序注释之类的,然后在通过配置接口,将我们需要的参数依次读取到内存中,进行读写.
常用的配置文件格式有:
- xml
- yaml
- json
- ini
- properties
- ......
具体的参数区别与优劣比较自己可以查阅 常用配置文件格式
protocbuf 介绍
Protocol buffers 是一种语言中立,平台无关,可扩展的序列化数据的格式,可用于通信协议,数据存储等。
Protocol buffers 在序列化数据方面,它是灵活的,高效的。相比于 XML 来说,Protocol buffers 更加小巧,更加快速,更加简单。一旦定义了要处理的数据的数据结构之后,就可以利用 Protocol buffers 的代码生成工具生成相关的代码。甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。
Protocol buffers 很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
使用说明
环境列表
本次使用的环境列表如下
- protobuf 3.11.0 protobuf-all-3.11.0.zip
- cmake 3.14.2
- VS2015
本机配置开源库编译
由于是在 Windows 下面编译, 所以这边使用了 cmake-gui 的编译方式, 按照如下步骤配置
0. 假设已经新建了一个文件夹,后续的操作都在此文件夹下 假设操作的所有文件夹都在 D:/Soft/Protobuf 文件夹下面, 新建三个文件夹,便于后续操作, 我们使用 $() 代替在你自己设备中的路径, 不要弄错
* source
$(SOURCE) 原始文件
* build
$(BUILD) 工程文件和编译文件
* install
$(INSTALL) 软件最后安装的路径
- 下载原始项目,如果压缩包,将全部文件解压到 $(SOURCE) 文件夹下面
-
使用 cmake-gui 配置相应的原始文件夹, 一般打开到 原始文件夹下有
CMakeLists.txt
的文件目录即可, 如:$(SOURCE)/protobuf-3.11.0/cmake
- 目标目录打开到 $(BUILD)
- 点击左下角,Configure, 选择 自己本机的 VS版本 可以选择架构 例如: VS2015 x64 架构
- 再次点击 Configure ,程序会开始读取 CMakeLists 里面的参数, 输出窗口会输出log 信息, 如果有红色会报错,我们需要排查相应的库是否完成依赖项,如果有重要依赖, 可能无法完成编译
- 如果一切无措,我们可以看到 如图的cmake 编译参数, 这是简略参数, 根据具体内容, 我们勾选相应的参数并设置相应的安装路径即可完成配置,
- 再次点击配置, 程序会进行配置, 选项会取消红色, 等生成完成即可,
- 点击 Generate 生成 VS2015 工程, 我们可以选择 open Project 快速打开,或者到 $(BUILD) 文件夹 点击对应的 sln 文件打开项目
使用 VS2015 编译库
- 一般 我们打开之后, 选择 Debug 或者 Release 选择 生成-生成解决方案, VS就开始了编译过程, 开始进行了项目编译过程
- 可能存在编译错误, 一般不会出现, 不建议更改源项目, 可以查看是否自己错误
- 编译好的之后的安装文件夹可能存在如下的文件夹结构, 将 $(INSTALL)/bin 加入到 path 环境中, 里面有 dll 文件和 exe 文件便于使用
├─bin
├─examples
├─include
└─lib
使用编译好的库 lib
-
假设我们需要测试 lib 文件, 建立 VS2015 工程, 新建
main.cpp
addressbook.proto
的文件 -
在我们使用编译好的库的时候, 需要引用 include 文件才能进行编译,
$(INSTALL)/include
-
我们使用 protobuf 的例程文件里面的
addressbook.proto
, 配置相应的消息格式,
自定义 message 格式
// See README.txt for information and build instructions.
//
// Note: START and END tags are used in comments to define sections used in
// tutorials. They are not part of the syntax for Protocol Buffers.
//
// To get an in-depth walkthrough of this file and the related examples, see:
// https://developers.google.com/protocol-buffers/docs/tutorials
// [START declaration]
syntax = "proto3";
package tutorial;
import "google/protobuf/timestamp.proto";
// [END declaration]
// [START java_declaration]
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
// [END java_declaration]
// [START csharp_declaration]
option csharp_namespace = "Google.Protobuf.Examples.AddressBook";
// [END csharp_declaration]
// [START messages]
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
google.protobuf.Timestamp last_updated = 5;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
// [END messages]
-
在工作目录下打开命令行, 使用bash 执行命令 将
.proto
文件编译成 对应的.h
和.cpp
文件
protoc ./addressbook.proto --cpp_out=./
- 将样例里面的 列出人员和新加人员复制到自定义文件中
根据测试样例改写的 Main.cpp
// See README.txt for information and build instructions.
#include <ctime>
#include <fstream>
#include <google/protobuf/util/time_util.h>
// #include <google/protobuf/>
#include <iostream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
using google::protobuf::util::TimeUtil;
// Iterates though all people in the AddressBook and prints info about them.
void ListPeople(const tutorial::AddressBook& address_book) {
for (int i = 0; i < address_book.people_size(); i++)
{
const tutorial::Person& person = address_book.people(i);
cout << "Person ID: " << person.id() << endl;
cout << " Name: " << person.name() << endl;
if (person.email() != "")
{
cout << " E-mail address: " << person.email() << endl;
}
for (int j = 0; j < person.phones_size(); j++)
{
const tutorial::Person::PhoneNumber& phone_number = person.phones(j);
switch (phone_number.type())
{
case tutorial::Person::MOBILE:
cout << " Mobile phone #: ";
break;
case tutorial::Person::HOME:
cout << " Home phone #: ";
break;
case tutorial::Person::WORK:
cout << " Work phone #: ";
break;
default:
cout << " Unknown phone #: ";
break;
}
cout << phone_number.number() << endl;
}
if (person.has_last_updated())
{
cout << " Updated: " << TimeUtil::ToString(person.last_updated()) << endl;
}
}
}
// This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {
cout << "Enter person ID number: ";
int id;
cin >> id;
person->set_id(id);
cin.ignore(256, ‘\n‘);
cout << "Enter name: ";
getline(cin, *person->mutable_name());
cout << "Enter email address (blank for none): ";
string email;
getline(cin, email);
if (!email.empty()) {
person->set_email(email);
}
while (true) {
cout << "Enter a phone number (or leave blank to finish): ";
string number;
getline(cin, number);
if (number.empty()) {
break;
}
tutorial::Person::PhoneNumber* phone_number = person->add_phones();
phone_number->set_number(number);
cout << "Is this a mobile, home, or work phone? ";
string type;
getline(cin, type);
if (type == "mobile") {
phone_number->set_type(tutorial::Person::MOBILE);
} else if (type == "home") {
phone_number->set_type(tutorial::Person::HOME);
} else if (type == "work") {
phone_number->set_type(tutorial::Person::WORK);
} else {
cout << "Unknown phone type. Using default." << endl;
}
}
*person->mutable_last_updated() = TimeUtil::SecondsToTimestamp(time(nullptr));
}
// 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.
int main(int argc, char* argv[]) {
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 2) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
}
tutorial::AddressBook address_book;
{
// Read the existing address book.
fstream input(argv[1], ios::in | ios::binary);
if (!input) {
cout << argv[1] << ": File not found. Creating a new file." << endl;
} else if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
}
// 列出文件 读取的人员
ListPeople(address_book);
// 重新添加一个人员
PromptForAddress(address_book.add_people());
{
// Write the new address book back to disk.
fstream output(argv[1], ios::out | ios::trunc | ios::binary);
if (!address_book.SerializeToOstream(&output)) {
cerr << "Failed to write address book." << endl;
return -1;
}
}
// Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
-
配置 main 函数的输入参数
项目--右键--属性--调试--命令参数----加入 ./test.bin -
配置 C++ 项目 include 文件夹和附加库
项目--右键--属性--VC++目录--包含目录--编辑-----加入 $(INSTALL)/include
项目--右键--属性--链接器--输入--附加依赖项-- 编辑-------加入 $(INSTALL)/lib/libprotobuf.lib 和 $(INSTALL)/lib/libprotobufd.lib -
如果编译了 共享dll 文件
项目--右键--属性--C/C++--预处理器--预处理器定义--编辑----加入 PROTOBUF_USE_DLLS
最终编译成功, 可以列出来 文件中的参数, 最终生成的二进制文件hex 文件内容
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: 0A 15 0A 01 32 10 01 1A 01 33 22 03 0A 01 34 2A ....2....3"...4*
00000010: 06 08 B2 AD F9 EE 05 0A 15 0A 01 32 10 01 1A 01 ..2-yn.....2....
00000020: 33 22 03 0A 01 34 2A 06 08 D2 AD F9 EE 05 3"...4*..R-yn.
更多
由于我们需要才自定义文件的后续添加大量的数据文件, 所以无法使用这种方式, 可能还是要使用更多的文件版本的方式来处理文件兼容性问题吧, 后续会将自己的参数设计思路以及参数实现方法