一、Protobuf通信协议概述
- 关于Protobuf通信协议语法可以参阅:https://blog.csdn.net/qq_41453285/article/details/106731318
- 关于Protobuf通信协议的实现可以参阅:
- 开源地址为:https://github.com/protocolbuffers/protobuf
二、protobuf库的安装
- 通过下方链接下载,此处我们下载3.12.1版本的。下载完解压,进入目录
-
# 如果下载失败,那就进入网页进行下载
-
wget https:
//distfiles.macports.org/protobuf3-cpp/protobuf-cpp-3.12.1.tar.gz
-
-
tar zxf protobuf-cpp
-3.12
.1.tar.gz
-
-
cd protobuf
-3.12
.1
- 进行配置、编译、安装
-
./configure
-
-
make
-
-
sudo
make install
- 默认情况下:
- 头文件默认安装在/usr/local/include/google/protobuf目录下
- 库文件默认安装在/usr/local/lib/目录下
- 可执行文件protoc默认安装在/usr/local/bin/目录下
- 重新加载动态库
sudo ldconfig
- 查看一下protobuf库的版本
protoc --version
编写带有protobuf库的C++程序
- 编译时要带上-lprotobuf选项,并且程序下要带有与protobuf相关的.h和.cc文件
三、演示案例
编写.protobuf文件
- 我们现在一份代码,主目录名为person,建立完成之后进入目录
- 现在我们在建立一个目录proto,用来存储.proto文件
- 现在我们编写两个.proto文件,分别名为IM.BaseDefine.proto、IM.Login.proto,文件内容分别如下所示
syntax = "proto3"; //声明protobuf版本, 此处为protobuf3, 如果不写默认为2 package IM.BaseDefine; //导致命名空间, 生成对应的.h和.cpp中会有一个IM命名空间和一个BaseDefine命名空间, 其中BaseDefine命名空间包含在IM命名空间内 option optimize_for = LITE_RUNTIME; //设置选项 enum PhoneType{ PHONE_DEFAULT = 0x0; PHONE_HOME = 0x0001; // 家庭电话 PHONE_WORK = 0x0002; // 工作电话 }
syntax = "proto3"; package IM.Login; import "IM.BaseDefine.proto"; //import引入IM.BaseDefine.proto文件 option optimize_for = LITE_RUNTIME; // message关键字 代表一个对象 message Phone{ string number = 1; // = 1是什么?默认值 IM.BaseDefine.PhoneType phone_type = 2; } message Book{ string name = 1; float price = 2; } message Person{ string name = 1; int32 age = 2; repeated string languages = 3; // repeated 重复, 可以嵌套对象, 类似数组, 会提供对应的add_languages()接口 Phone phone = 4; repeated Book books = 5; bool vip = 6; string address = 7; } //使用T开头测试 message TInt32{ int32 int1 = 1; } message TString{ string str1 = 1; }
- 然后建立一个脚本create.sh并赋予其可执行权限,用来编译.proto文件生成C++代码文件,脚本内容如下:
#!/bin/sh # proto文件在哪里 SRC_DIR=./ # .h .cc输出到哪里 DST_DIR=../ #C++ protoc -I= $SRC_DIR --cpp_out= $DST_DIR $SRC_DIR/*.proto
- 现在.proto目录下有如下三个文件:
生成.h和.cc文件
- 指向上面的create.sh,就会在proto的上一级目录自动生成对应的.h和.cc文件,如下所示:
- IM.BaseDefine.proto:会生成IM.BaseDefine.pb.h和IM.BaseDefine.pb.cc
- IM.Login.proto:会生成IM.Login.pb.h和IM.Login.pb.cc
- .h文件中会根据.proto文件生成对应的接口,.cc文件则是.h文件的实现。下面截取IM.BaseDefine.pb.h的部分代码,关于具体实现就不详细介绍了
编写测试代码①
- 有了上面对应的.h和.cc代码之后,我们就可以自己编写C++程序来测试上面的接口了
- 例如下面是一个code_test.cpp的文件,用来测试相关接口的使用
//code_test.cpp #include <iostream> #include <stdio.h> #include <unistd.h> #include <sys/time.h> #include <sys/wait.h> #include "IM.BaseDefine.pb.h" #include "IM.Login.pb.h" static uint64_t getNowTime() { struct timeval tval; uint64_t nowTime; gettimeofday(&tval, NULL); nowTime = tval.tv_sec * 1000L + tval.tv_usec / 1000L; return nowTime; } /* 如果proto结构体的变量是基础变量,比如int、string等等,那么set的时候直接调用set_xxx即可。 如果变量是自定义类型(也就是message嵌套),那么C++的生成代码中,就没有set_xxx函数名,取而代之的是三个函数名: set_allocated_xxx() release_xxx() mutable_xxx() 使用set_allocated_xxx()来设置变量的时候,变量不能是普通栈内存数据, 必须是手动new出来的指针,至于何时delete,就不需要调用者关心了, protobuf内部会自动delete掉通过set_allocated_设置的内存; release_xxx()是用来取消之前由set_allocated_xxx设置的数据, 调用release_xxx以后,protobuf内部就不会自动去delete这个数据; mutable_xxx()返回分配内存后的对象,如果已经分配过则直接返回,如果没有分配则在内部分配,建议使用mutable_xxx */ bool ProtobufEncode(std::string &strPb) { IM::Login::Person person; person.set_name( "dongshao"); // 设置以set_为前缀 person.set_age( 80); person.add_languages( "C++"); // 数组add person.add_languages( "Java"); // 电话号码 // mutable_ 嵌套对象时使用,并且是单个对象时使用,比如对应的Person里面的Phone phone = 4; // 比如mutable_phone如果phone已经存在则直接返回,如果不存在则new 一个返回 IM::Login::Phone *phone = person.mutable_phone(); if(!phone) { std:: cout << "mutable_phone failed." << std:: endl; return false; } phone->set_number( "18888888888"); phone->set_phone_type(IM::BaseDefine::PHONE_HOME); // 书籍 // add_则是针对repeated的嵌套对象,每次调用都返回一个新的对象,注意和mutable_的区别。 // 比如Person里面的repeated Book books = 5; IM::Login::Book *book = person.add_books(); book->set_name( "Linux kernel development"); book->set_price( 7.7); book = person.add_books(); book->set_name( "Linux server development"); book->set_price( 8.0); // vip person.set_vip( true); // 地址 person.set_address( "anhui"); uint32_t pbSize = person.ByteSize(); // 序列化后的大小 strPb.clear(); strPb.resize(pbSize); uint8_t *szData = ( uint8_t *)strPb.c_str(); if (!person.SerializeToArray(szData, pbSize)) // 拷贝序列化后的数据 { std:: cout << "person pb msg SerializeToArray failed." << std:: endl; return false; } return true; } bool ProtobufDecode(std::string &strPb) { IM::Login::Person person; person.ParseFromArray(strPb.c_str(), strPb.size()); // 反序列化 // printPerson(person); 这个函数在本文件被删除了, 实现可以参阅pb_speed.cpp return true; } void printHex(uint8_t *data, uint32_t len) { for( uint32_t i = 0; i < len; i++) { printf( "%02x ", data[i]); } printf( "\n\n"); } void TInt() { std:: string strPb; uint8_t *szData; IM::Login::TInt32 int1; uint32_t int1Size = int1.ByteSize(); // 序列化后的大小 std:: cout << "null int1Size = " << int1Size << std:: endl; int1.set_int1( 0x12); int1Size = int1.ByteSize(); // 序列化后的大小 std:: cout << "0x12 int1Size = " << int1Size << std:: endl; strPb.clear(); strPb.resize(int1Size); szData = ( uint8_t *)strPb.c_str(); int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据 printHex(szData, int1Size); int1.set_int1( -11); int1Size = int1.ByteSize(); // 序列化后的大小 std:: cout << "-11 int1Size = " << int1Size << std:: endl; strPb.clear(); strPb.resize(int1Size); szData = ( uint8_t *)strPb.c_str(); int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据 printHex(szData, int1Size); int1.set_int1( 0x7f); int1Size = int1.ByteSize(); // 序列化后的大小 std:: cout << "0xff int1Size = " << int1Size << std:: endl; strPb.clear(); strPb.resize(int1Size); szData = ( uint8_t *)strPb.c_str(); int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据 printHex(szData, int1Size); int1.set_int1( 0xff); int1Size = int1.ByteSize(); // 序列化后的大小 std:: cout << "0xff int1Size = " << int1Size << std:: endl; strPb.clear(); strPb.resize(int1Size); szData = ( uint8_t *)strPb.c_str(); int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据 printHex(szData, int1Size); int1.set_int1( 0x1234); int1Size = int1.ByteSize(); // 序列化后的大小 std:: cout << "0x1234 int1Size = " << int1Size << std:: endl; strPb.clear(); strPb.resize(int1Size); szData = ( uint8_t *)strPb.c_str(); int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据 printHex(szData, int1Size); int1.set_int1( 0x123456); int1Size = int1.ByteSize(); // 序列化后的大小 std:: cout << "0x123456 int1Size = " << int1Size << std:: endl; strPb.clear(); strPb.resize(int1Size); szData = ( uint8_t *)strPb.c_str(); int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据 printHex(szData, int1Size); } void TString(void) { std:: string strPb; uint8_t *szData; IM::Login::TString str1; uint32_t str1Size = str1.ByteSize(); // 序列化后的大小 std:: cout << "null str1Size = " << str1Size << std:: endl; str1.set_str1( "1"); str1Size = str1.ByteSize(); // 序列化后的大小 std:: cout << "1 str1Size = " << str1Size << std:: endl; strPb.clear(); strPb.resize(str1Size); szData = ( uint8_t *)strPb.c_str(); str1.SerializeToArray(szData, str1Size); // 拷贝序列化后的数据 printHex(szData, str1Size); str1.set_str1( "1234"); str1Size = str1.ByteSize(); // 序列化后的大小 std:: cout << "1234 str1Size = " << str1Size << std:: endl; strPb.clear(); strPb.resize(str1Size); szData = ( uint8_t *)strPb.c_str(); str1.SerializeToArray(szData, str1Size); // 拷贝序列化后的数据 printHex(szData, str1Size); str1.set_str1( "老师"); str1Size = str1.ByteSize(); // 序列化后的大小 std:: cout << "老师 str1Size = " << str1Size << std:: endl; strPb.clear(); strPb.resize(str1Size); szData = ( uint8_t *)strPb.c_str(); str1.SerializeToArray(szData, str1Size); // 拷贝序列化后的数据 printHex(szData, str1Size); } int main(void) { TInt(); TString(); return 0; }
- 使用下面的命令进行编译,编译时可能会警告,可以忽略
g++ -o code_test code_test.cpp IM.BaseDefine.pb.cc IM.Login.pb.cc -lprotobuf -lpthread
- 运行程序,效果如下:
编写测试代码②
- 下面我们再编写一个pb_speed.cpp,用来测试相关接口性能
//pb_speed.cpp #include <iostream> #include <stdio.h> #include <unistd.h> #include <sys/time.h> #include <sys/wait.h> #include "IM.BaseDefine.pb.h" #include "IM.Login.pb.h" static uint64_t getNowTime() { struct timeval tval; uint64_t nowTime; gettimeofday(&tval, NULL); nowTime = tval.tv_sec * 1000L + tval.tv_usec / 1000L; return nowTime; } /* 如果proto结构体的变量是基础变量,比如int、string等等,那么set的时候直接调用set_xxx即可。 如果变量是自定义类型(也就是message嵌套),那么C++的生成代码中,就没有set_xxx函数名,取而代之的是三个函数名: set_allocated_xxx() release_xxx() mutable_xxx() 使用set_allocated_xxx()来设置变量的时候,变量不能是普通栈内存数据, 必须是手动new出来的指针,至于何时delete,就不需要调用者关心了, protobuf内部会自动delete掉通过set_allocated_设置的内存; release_xxx()是用来取消之前由set_allocated_xxx设置的数据, 调用release_xxx以后,protobuf内部就不会自动去delete这个数据; mutable_xxx()返回分配内存后的对象,如果已经分配过则直接返回,如果没有分配则在内部分配,建议使用mutable_xxx */ bool ProtobufEncode(std::string &strPb) { IM::Login::Person person; person.set_name( "dongshao"); // 设置以set_为前缀 person.set_age( 80); person.add_languages( "C++"); // 数组add 自带类型 protobuff关键字支持的 person.add_languages( "Java"); // 电话号码 // mutable_ 嵌套对象时使用,并且是单个对象时使用,比如对应的Person里面的Phone phone = 4; // 比如mutable_phone如果phone已经存在则直接返回,如果不存在则new 一个返回 IM::Login::Phone *phone = person.mutable_phone(); if(!phone) { std:: cout << "mutable_phone failed." << std:: endl; return false; } phone->set_number( "18888888888"); phone->set_phone_type(IM::BaseDefine::PHONE_HOME); // 书籍 // add_则是针对repeated的嵌套对象,每次调用都返回一个新的对象,注意和mutable_的区别。 // 比如Person里面的repeated Book books = 5; IM::Login::Book *book = person.add_books(); book->set_name( "Linux kernel development"); book->set_price( 7.7); book = person.add_books(); book->set_name( "Linux server development"); book->set_price( 8.0); // vip person.set_vip( true); // 地址 person.set_address( "anhui"); uint32_t pbSize = person.ByteSize(); // 获取序列化后的大小 strPb.clear(); strPb.resize(pbSize); uint8_t *szData = ( uint8_t *)strPb.c_str(); if (!person.SerializeToArray(szData, pbSize)) // 拷贝序列化后的数据 { std:: cout << "person pb msg SerializeToArray failed." << std:: endl; return false; } return true; } static void printPerson(IM::Login::Person &person) { std:: cout << "name:\t" << person.name() << std:: endl; std:: cout << "age:\t" << person.age() << std:: endl; std:: string languages; for ( int i = 0; i < person.languages_size(); i++) { if (i != 0) { languages += ", "; } languages += person.languages(i); } std:: cout << "languages:\t" << languages << std:: endl; if (person.has_phone()) // 自定义message的嵌套并且不是设置为repeated则有has_ { // 注意引用 const IM::Login::Phone &phone = person.phone(); std:: cout << "phone number:\t" << phone.number() << ", type:\t" << phone.phone_type() << std:: endl; } else { std:: cout << "no phone" << std:: endl; } for ( int i = 0; i < person.books_size(); i++) { const IM::Login::Book &book = person.books(i); std:: cout << "book name:\t" << book.name() << ", price:\t" << book.price() << std:: endl; } std:: cout << "vip:\t" << person.vip() << std:: endl; std:: cout << "address:\t" << person.address() << std:: endl; } bool ProtobufDecode(std::string &strPb) { IM::Login::Person person; person.ParseFromArray(strPb.c_str(), strPb.size()); // 反序列化 printPerson(person); return true; } #define TEST_COUNT 1000000 int main(void) { std:: string strPb; ProtobufEncode(strPb); // 序列化后是二进制 std:: cout << "ProtobufDecode, size: " << strPb.size() << std:: endl; ProtobufDecode(strPb); #if 1 uint64_t startTime; uint64_t nowTime; startTime = getNowTime(); std:: cout << "protobuf encode time testing" << std:: endl; for ( int i = 0; i < TEST_COUNT; i++) { ProtobufEncode(strPb); } nowTime = getNowTime(); std:: cout << "protobuf encode " << TEST_COUNT << " time, need time: " << nowTime - startTime << "ms" << std:: endl; startTime = getNowTime(); std:: cout << "protobuf decode time testing" << std:: endl; for ( int i = 0; i < TEST_COUNT; i++) { ProtobufDecode(strPb); } nowTime = getNowTime(); std:: cout << "protobuf decode " << TEST_COUNT << " time, need time: " << nowTime - startTime << "ms" << std:: endl; #endif return 0; }
- 使用下面的命令进行编译,编译时可能会警告,可以忽略
g++ -o pb_speed pb_speed.cpp IM.BaseDefine.pb.cc IM.Login.pb.cc -lprotobuf -lpthread
- 运行程序,效果如下:
转载:https://blog.csdn.net/qq_41453285/article/details/106745639
查看评论