愿打开此篇对你有所帮助。

protobuf篇:介绍、安装、官方教程翻译、测试_原力计划


文章目录



            PB、JSON、XML

            protobuf篇:介绍、安装、官方教程翻译、测试_protobuf_02

            综上所述目前最好的设计消息数据包方式是服务器和客户端通信协议推荐用protobuf,服务器存入数据库时用json。


            安装

            工欲善其事,必先利其器。

            git clone https://github.com/protocolbuffers/protobuf.git
            yum install -y autoreconf automake libtool
            cd protobuf/
            /autogen.sh
            ./configure
            遇到警告
            cd ..
            网址:wget https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz
            报错那个文件夹下是有googletest的,给它删了,用上面链接下载的顶上去(记得先解压)。
            回到第四行重来,就不会有警告了。

            接下来转这里:https://lion-wu.blog.csdn.net/article/details/120974985


            如何写一个PB文件

            也有proto3,都可以吧,我最开始接触的是2,就翻译 2 吧。

            官网地址:https://developers.google.cn/protocol-buffers/docs/proto,有兴趣的朋友可以自行查阅。

            好吧,建议自己打开看,我不全翻译,就看到一些觉得比较重要的我搬过来。

            Protobuf消息定义

            你首先需要在一个 .proto 文件中定义你需要做串行化的数据结构信息。每个ProtocolBuffer信息是一小段逻辑记录,包含一系列的键值对。

            消息由至少一个字段组合而成,类似于C语言中的结构。每个字段都有一定的格式。

            字段格式:限定修饰符① | 数据类型② | 字段名称③ | = | 字段编码值④ | [字段默认值⑤]

            这里有个非常简单的 .proto 文件定义了个人信息:

            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;
            }

            ①限定修饰符

            Required: 表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。

            Optional:表示是一个可选字段,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。—因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。

            Repeated:表示该字段可以包含0~N个元素。其特性和optional一样,但是每一次可以包含多个值。可以看作是在传递一个数组的值。

            ②数据类型速查

            Protobuf定义了一套基本数据类型。几乎都可以映射到C++\Java等语言的基础数据类型.

            protobuf篇:介绍、安装、官方教程翻译、测试_原力计划_03

            1. N 表示打包的字节并不是固定。而是根据数据的大小或者长度。
            2. 关于message,类似于C语言中的结构包含另外一个结构作为数据成员一样。
            3. 关于 fixed32 和int32的区别。fixed32的打包效率比int32的效率高,但是使用的空间一般比int32多。因此一个属于时间效率高,一个属于空间效率高。根据项目的实际情况,一般选择fixed32,如果遇到对传输数据量要求比较苛刻的环境,可以选择int32.

            ④字段编码值

            编码值的取值范围为 1~2^32(4294967296)。

            不信往上面翻翻看,看看是不是都是数值。

            其中 1~15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低(相对于1-15),当然一般情况下相邻的2个值编码效率的是相同的,除非2个值恰好实在4字节,12字节,20字节等的临界区。比如15和16.

            1900~2000编码值为Google protobuf 系统内部保留值,建议不要在自己的项目中使用。

            protobuf 还建议把经常要传递的值把其字段编码设置为1-15之间的值。

            消息中的字段的编码值无需连续,只要是合法的,并且不能在同一个消息中有字段包含相同的编码值。

            ⑤默认值。

            当在传递数据时,对于required数据类型,如果用户没有设置值,则使用默认值传递到对端。当接受数据是,对于optional字段,如果没有接收到optional字段,则设置为默认值。


            几个注意事项

            import

            protobuf 接口文件可以像C语言的h文件一样,分离为多个,在需要的时候通过 import导入需要的文件。

            虽然可以在单个.proto文件中定义多种消息类型(例如消息,枚举和服务),但当在单个文件中定义大量具有不同依赖性的消息时,也

            可能导致依赖性膨胀。建议每个.proto文件包含尽可能少的消息类型。

            关于package

            避免名称冲突,可以给每个文件指定一个package名称,对于C++解析为名称空间。

            记得在开头加上这两句:

            syntax = "proto3";

            package demo;

            关于enum

            枚举的定义和C++相同,但是有一些限制。

            1. 枚举值必须大于等于0的整数。
            2. 使用分号(;)分隔枚举变量而不是C++语言中的逗号(,)

            编译PB

            可编译文件

            首先,你要有一个PB文件可以拿去编译,我知道你多半也没有,没事我这里有。

            syntax = "proto3";

            package demo;
            message Person {
            string name = 1;
            int32 id = 2;
            string email = 3;

            enum PhoneType {
            MOBILE = 0;
            HOME = 1;
            WORK = 2;
            }

            message PhoneNumber {
            string number = 1;
            PhoneType type = 2;
            }
            repeated PhoneNumber phone = 4;
            }


            开始编译

            我下载了好多款,不过主语言是C++,毕竟打开一次不容易。打不开下载目录的小伙伴可以私信我拿。

            打开cmd, cd到该目录,protoc.exe的命令行参数格式如下:

            protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto

            protobuf篇:介绍、安装、官方教程翻译、测试_字段_04

            编译之后就会出现新的文件了。

            阅览文件

            打开之后太长了,自己去看。我这里提几点。

            (1)proto中的package在C++中是namespace;

            (2)proto中的message在C++中是class,类里面有各个成员的set/get;基类是google::protobuf::Message。

            (3)代码中可以看见C++11中的移动构造和移动赋值函数。


            搞技术的人不搞那些弯弯绕的,放码过来吧。

            放码过来

            proto

            syntax = "proto2";

            package tutorial;

            message Person {
            optional string name = 1;
            optional int32 id = 2;
            optional string email = 3;

            enum PhoneType {
            MOBILE = 0;
            HOME = 1;
            WORK = 2;
            }

            message PhoneNumber {
            optional string number = 1;
            optional PhoneType type = 2 [default = HOME];
            }

            repeated PhoneNumber phones = 4;
            }

            message AddressBook {
            repeated Person people = 1;
            }


            读.cpp

            #include <iostream>
            #include <fstream>
            #include <string>
            #include "person.pb.h"
            using namespace std;

            // 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;
            }
            }
            }

            // 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;
            }
            }

            // Add an address.
            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;
            }


            写.cpp

            #include <iostream>
            #include <fstream>
            #include <string>
            #include "addressbook.pb.h"
            using namespace std;

            // 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.has_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;
            }
            cout << phone_number.number() << endl;
            }
            }
            }

            // Main function: Reads the entire address book from a file and prints all
            // the information inside.
            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 (!address_book.ParseFromIstream(&input)) {
            cerr << "Failed to parse address book." << endl;
            return -1;
            }
            }

            ListPeople(address_book);

            // Optional: Delete all global objects allocated by libprotobuf.
            google::protobuf::ShutdownProtobufLibrary();

            return 0;
            }