一、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
查看评论
					 
					











