飞道的博客

Linux(程序设计):33---protobuf库(C++操作Protobuf通信协议)

947人阅读  评论(0)

一、Protobuf通信协议概述

二、protobuf库的安装

  • 通过下方链接下载,此处我们下载3.12.1版本的。下载完解压,进入目录

  
  1. # 如果下载失败,那就进入网页进行下载
  2. wget https: //distfiles.macports.org/protobuf3-cpp/protobuf-cpp-3.12.1.tar.gz
  3. tar zxf protobuf-cpp -3.12 .1.tar.gz
  4. cd protobuf -3.12 .1

  • 进行配置、编译、安装

  
  1. ./configure
  2. make
  3. 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,文件内容分别如下所示


   
  1. syntax = "proto3"; //声明protobuf版本, 此处为protobuf3, 如果不写默认为2
  2. package IM.BaseDefine; //导致命名空间, 生成对应的.h和.cpp中会有一个IM命名空间和一个BaseDefine命名空间, 其中BaseDefine命名空间包含在IM命名空间内
  3. option optimize_for = LITE_RUNTIME; //设置选项
  4. enum PhoneType{
  5. PHONE_DEFAULT = 0x0;
  6. PHONE_HOME = 0x0001; // 家庭电话
  7. PHONE_WORK = 0x0002; // 工作电话
  8. }

   
  1. syntax = "proto3";
  2. package IM.Login;
  3. import "IM.BaseDefine.proto"; //import引入IM.BaseDefine.proto文件
  4. option optimize_for = LITE_RUNTIME;
  5. // message关键字 代表一个对象
  6. message Phone{
  7. string number = 1; // = 1是什么?默认值
  8. IM.BaseDefine.PhoneType phone_type = 2;
  9. }
  10. message Book{
  11. string name = 1;
  12. float price = 2;
  13. }
  14. message Person{
  15. string name = 1;
  16. int32 age = 2;
  17. repeated string languages = 3; // repeated 重复, 可以嵌套对象, 类似数组, 会提供对应的add_languages()接口
  18. Phone phone = 4;
  19. repeated Book books = 5;
  20. bool vip = 6;
  21. string address = 7;
  22. }
  23. //使用T开头测试
  24. message TInt32{
  25. int32 int1 = 1;
  26. }
  27. message TString{
  28. string str1 = 1;
  29. }
  • 然后建立一个脚本create.sh并赋予其可执行权限,用来编译.proto文件生成C++代码文件,脚本内容如下:


   
  1. #!/bin/sh
  2. # proto文件在哪里
  3. SRC_DIR=./
  4. # .h .cc输出到哪里
  5. DST_DIR=../
  6. #C++
  7. 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的文件,用来测试相关接口的使用


   
  1. //code_test.cpp
  2. #include <iostream>
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. #include <sys/time.h>
  6. #include <sys/wait.h>
  7. #include "IM.BaseDefine.pb.h"
  8. #include "IM.Login.pb.h"
  9. static uint64_t getNowTime()
  10. {
  11. struct timeval tval;
  12. uint64_t nowTime;
  13. gettimeofday(&tval, NULL);
  14. nowTime = tval.tv_sec * 1000L + tval.tv_usec / 1000L;
  15. return nowTime;
  16. }
  17. /*
  18. 如果proto结构体的变量是基础变量,比如int、string等等,那么set的时候直接调用set_xxx即可。
  19. 如果变量是自定义类型(也就是message嵌套),那么C++的生成代码中,就没有set_xxx函数名,取而代之的是三个函数名:
  20. set_allocated_xxx()
  21. release_xxx()
  22. mutable_xxx()
  23. 使用set_allocated_xxx()来设置变量的时候,变量不能是普通栈内存数据,
  24. 必须是手动new出来的指针,至于何时delete,就不需要调用者关心了,
  25. protobuf内部会自动delete掉通过set_allocated_设置的内存;
  26. release_xxx()是用来取消之前由set_allocated_xxx设置的数据,
  27. 调用release_xxx以后,protobuf内部就不会自动去delete这个数据;
  28. mutable_xxx()返回分配内存后的对象,如果已经分配过则直接返回,如果没有分配则在内部分配,建议使用mutable_xxx
  29. */
  30. bool ProtobufEncode(std::string &strPb)
  31. {
  32. IM::Login::Person person;
  33. person.set_name( "dongshao"); // 设置以set_为前缀
  34. person.set_age( 80);
  35. person.add_languages( "C++"); // 数组add
  36. person.add_languages( "Java");
  37. // 电话号码
  38. // mutable_ 嵌套对象时使用,并且是单个对象时使用,比如对应的Person里面的Phone phone = 4;
  39. // 比如mutable_phone如果phone已经存在则直接返回,如果不存在则new 一个返回
  40. IM::Login::Phone *phone = person.mutable_phone();
  41. if(!phone)
  42. {
  43. std:: cout << "mutable_phone failed." << std:: endl;
  44. return false;
  45. }
  46. phone->set_number( "18888888888");
  47. phone->set_phone_type(IM::BaseDefine::PHONE_HOME);
  48. // 书籍
  49. // add_则是针对repeated的嵌套对象,每次调用都返回一个新的对象,注意和mutable_的区别。
  50. // 比如Person里面的repeated Book books = 5;
  51. IM::Login::Book *book = person.add_books();
  52. book->set_name( "Linux kernel development");
  53. book->set_price( 7.7);
  54. book = person.add_books();
  55. book->set_name( "Linux server development");
  56. book->set_price( 8.0);
  57. // vip
  58. person.set_vip( true);
  59. // 地址
  60. person.set_address( "anhui");
  61. uint32_t pbSize = person.ByteSize(); // 序列化后的大小
  62. strPb.clear();
  63. strPb.resize(pbSize);
  64. uint8_t *szData = ( uint8_t *)strPb.c_str();
  65. if (!person.SerializeToArray(szData, pbSize)) // 拷贝序列化后的数据
  66. {
  67. std:: cout << "person pb msg SerializeToArray failed." << std:: endl;
  68. return false;
  69. }
  70. return true;
  71. }
  72. bool ProtobufDecode(std::string &strPb)
  73. {
  74. IM::Login::Person person;
  75. person.ParseFromArray(strPb.c_str(), strPb.size()); // 反序列化
  76. // printPerson(person); 这个函数在本文件被删除了, 实现可以参阅pb_speed.cpp
  77. return true;
  78. }
  79. void printHex(uint8_t *data, uint32_t len)
  80. {
  81. for( uint32_t i = 0; i < len; i++)
  82. {
  83. printf( "%02x ", data[i]);
  84. }
  85. printf( "\n\n");
  86. }
  87. void TInt()
  88. {
  89. std:: string strPb;
  90. uint8_t *szData;
  91. IM::Login::TInt32 int1;
  92. uint32_t int1Size = int1.ByteSize(); // 序列化后的大小
  93. std:: cout << "null int1Size = " << int1Size << std:: endl;
  94. int1.set_int1( 0x12);
  95. int1Size = int1.ByteSize(); // 序列化后的大小
  96. std:: cout << "0x12 int1Size = " << int1Size << std:: endl;
  97. strPb.clear();
  98. strPb.resize(int1Size);
  99. szData = ( uint8_t *)strPb.c_str();
  100. int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据
  101. printHex(szData, int1Size);
  102. int1.set_int1( -11);
  103. int1Size = int1.ByteSize(); // 序列化后的大小
  104. std:: cout << "-11 int1Size = " << int1Size << std:: endl;
  105. strPb.clear();
  106. strPb.resize(int1Size);
  107. szData = ( uint8_t *)strPb.c_str();
  108. int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据
  109. printHex(szData, int1Size);
  110. int1.set_int1( 0x7f);
  111. int1Size = int1.ByteSize(); // 序列化后的大小
  112. std:: cout << "0xff int1Size = " << int1Size << std:: endl;
  113. strPb.clear();
  114. strPb.resize(int1Size);
  115. szData = ( uint8_t *)strPb.c_str();
  116. int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据
  117. printHex(szData, int1Size);
  118. int1.set_int1( 0xff);
  119. int1Size = int1.ByteSize(); // 序列化后的大小
  120. std:: cout << "0xff int1Size = " << int1Size << std:: endl;
  121. strPb.clear();
  122. strPb.resize(int1Size);
  123. szData = ( uint8_t *)strPb.c_str();
  124. int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据
  125. printHex(szData, int1Size);
  126. int1.set_int1( 0x1234);
  127. int1Size = int1.ByteSize(); // 序列化后的大小
  128. std:: cout << "0x1234 int1Size = " << int1Size << std:: endl;
  129. strPb.clear();
  130. strPb.resize(int1Size);
  131. szData = ( uint8_t *)strPb.c_str();
  132. int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据
  133. printHex(szData, int1Size);
  134. int1.set_int1( 0x123456);
  135. int1Size = int1.ByteSize(); // 序列化后的大小
  136. std:: cout << "0x123456 int1Size = " << int1Size << std:: endl;
  137. strPb.clear();
  138. strPb.resize(int1Size);
  139. szData = ( uint8_t *)strPb.c_str();
  140. int1.SerializeToArray(szData, int1Size); // 拷贝序列化后的数据
  141. printHex(szData, int1Size);
  142. }
  143. void TString(void)
  144. {
  145. std:: string strPb;
  146. uint8_t *szData;
  147. IM::Login::TString str1;
  148. uint32_t str1Size = str1.ByteSize(); // 序列化后的大小
  149. std:: cout << "null str1Size = " << str1Size << std:: endl;
  150. str1.set_str1( "1");
  151. str1Size = str1.ByteSize(); // 序列化后的大小
  152. std:: cout << "1 str1Size = " << str1Size << std:: endl;
  153. strPb.clear();
  154. strPb.resize(str1Size);
  155. szData = ( uint8_t *)strPb.c_str();
  156. str1.SerializeToArray(szData, str1Size); // 拷贝序列化后的数据
  157. printHex(szData, str1Size);
  158. str1.set_str1( "1234");
  159. str1Size = str1.ByteSize(); // 序列化后的大小
  160. std:: cout << "1234 str1Size = " << str1Size << std:: endl;
  161. strPb.clear();
  162. strPb.resize(str1Size);
  163. szData = ( uint8_t *)strPb.c_str();
  164. str1.SerializeToArray(szData, str1Size); // 拷贝序列化后的数据
  165. printHex(szData, str1Size);
  166. str1.set_str1( "老师");
  167. str1Size = str1.ByteSize(); // 序列化后的大小
  168. std:: cout << "老师 str1Size = " << str1Size << std:: endl;
  169. strPb.clear();
  170. strPb.resize(str1Size);
  171. szData = ( uint8_t *)strPb.c_str();
  172. str1.SerializeToArray(szData, str1Size); // 拷贝序列化后的数据
  173. printHex(szData, str1Size);
  174. }
  175. int main(void)
  176. {
  177. TInt();
  178. TString();
  179. return 0;
  180. }
  • 使用下面的命令进行编译,编译时可能会警告,可以忽略
g++ -o code_test code_test.cpp IM.BaseDefine.pb.cc IM.Login.pb.cc -lprotobuf -lpthread

  • 运行程序,效果如下:

编写测试代码②

  • 下面我们再编写一个pb_speed.cpp,用来测试相关接口性能


   
  1. //pb_speed.cpp
  2. #include <iostream>
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. #include <sys/time.h>
  6. #include <sys/wait.h>
  7. #include "IM.BaseDefine.pb.h"
  8. #include "IM.Login.pb.h"
  9. static uint64_t getNowTime()
  10. {
  11. struct timeval tval;
  12. uint64_t nowTime;
  13. gettimeofday(&tval, NULL);
  14. nowTime = tval.tv_sec * 1000L + tval.tv_usec / 1000L;
  15. return nowTime;
  16. }
  17. /*
  18. 如果proto结构体的变量是基础变量,比如int、string等等,那么set的时候直接调用set_xxx即可。
  19. 如果变量是自定义类型(也就是message嵌套),那么C++的生成代码中,就没有set_xxx函数名,取而代之的是三个函数名:
  20. set_allocated_xxx()
  21. release_xxx()
  22. mutable_xxx()
  23. 使用set_allocated_xxx()来设置变量的时候,变量不能是普通栈内存数据,
  24. 必须是手动new出来的指针,至于何时delete,就不需要调用者关心了,
  25. protobuf内部会自动delete掉通过set_allocated_设置的内存;
  26. release_xxx()是用来取消之前由set_allocated_xxx设置的数据,
  27. 调用release_xxx以后,protobuf内部就不会自动去delete这个数据;
  28. mutable_xxx()返回分配内存后的对象,如果已经分配过则直接返回,如果没有分配则在内部分配,建议使用mutable_xxx
  29. */
  30. bool ProtobufEncode(std::string &strPb)
  31. {
  32. IM::Login::Person person;
  33. person.set_name( "dongshao"); // 设置以set_为前缀
  34. person.set_age( 80);
  35. person.add_languages( "C++"); // 数组add 自带类型 protobuff关键字支持的
  36. person.add_languages( "Java");
  37. // 电话号码
  38. // mutable_ 嵌套对象时使用,并且是单个对象时使用,比如对应的Person里面的Phone phone = 4;
  39. // 比如mutable_phone如果phone已经存在则直接返回,如果不存在则new 一个返回
  40. IM::Login::Phone *phone = person.mutable_phone();
  41. if(!phone)
  42. {
  43. std:: cout << "mutable_phone failed." << std:: endl;
  44. return false;
  45. }
  46. phone->set_number( "18888888888");
  47. phone->set_phone_type(IM::BaseDefine::PHONE_HOME);
  48. // 书籍
  49. // add_则是针对repeated的嵌套对象,每次调用都返回一个新的对象,注意和mutable_的区别。
  50. // 比如Person里面的repeated Book books = 5;
  51. IM::Login::Book *book = person.add_books();
  52. book->set_name( "Linux kernel development");
  53. book->set_price( 7.7);
  54. book = person.add_books();
  55. book->set_name( "Linux server development");
  56. book->set_price( 8.0);
  57. // vip
  58. person.set_vip( true);
  59. // 地址
  60. person.set_address( "anhui");
  61. uint32_t pbSize = person.ByteSize(); // 获取序列化后的大小
  62. strPb.clear();
  63. strPb.resize(pbSize);
  64. uint8_t *szData = ( uint8_t *)strPb.c_str();
  65. if (!person.SerializeToArray(szData, pbSize)) // 拷贝序列化后的数据
  66. {
  67. std:: cout << "person pb msg SerializeToArray failed." << std:: endl;
  68. return false;
  69. }
  70. return true;
  71. }
  72. static void printPerson(IM::Login::Person &person)
  73. {
  74. std:: cout << "name:\t" << person.name() << std:: endl;
  75. std:: cout << "age:\t" << person.age() << std:: endl;
  76. std:: string languages;
  77. for ( int i = 0; i < person.languages_size(); i++)
  78. {
  79. if (i != 0)
  80. {
  81. languages += ", ";
  82. }
  83. languages += person.languages(i);
  84. }
  85. std:: cout << "languages:\t" << languages << std:: endl;
  86. if (person.has_phone()) // 自定义message的嵌套并且不是设置为repeated则有has_
  87. {
  88. // 注意引用
  89. const IM::Login::Phone &phone = person.phone();
  90. std:: cout << "phone number:\t" << phone.number() << ", type:\t" << phone.phone_type() << std:: endl;
  91. }
  92. else
  93. {
  94. std:: cout << "no phone" << std:: endl;
  95. }
  96. for ( int i = 0; i < person.books_size(); i++)
  97. {
  98. const IM::Login::Book &book = person.books(i);
  99. std:: cout << "book name:\t" << book.name() << ", price:\t" << book.price() << std:: endl;
  100. }
  101. std:: cout << "vip:\t" << person.vip() << std:: endl;
  102. std:: cout << "address:\t" << person.address() << std:: endl;
  103. }
  104. bool ProtobufDecode(std::string &strPb)
  105. {
  106. IM::Login::Person person;
  107. person.ParseFromArray(strPb.c_str(), strPb.size()); // 反序列化
  108. printPerson(person);
  109. return true;
  110. }
  111. #define TEST_COUNT 1000000
  112. int main(void)
  113. {
  114. std:: string strPb;
  115. ProtobufEncode(strPb); // 序列化后是二进制
  116. std:: cout << "ProtobufDecode, size: " << strPb.size() << std:: endl;
  117. ProtobufDecode(strPb);
  118. #if 1
  119. uint64_t startTime;
  120. uint64_t nowTime;
  121. startTime = getNowTime();
  122. std:: cout << "protobuf encode time testing" << std:: endl;
  123. for ( int i = 0; i < TEST_COUNT; i++)
  124. {
  125. ProtobufEncode(strPb);
  126. }
  127. nowTime = getNowTime();
  128. std:: cout << "protobuf encode " << TEST_COUNT << " time, need time: "
  129. << nowTime - startTime << "ms" << std:: endl;
  130. startTime = getNowTime();
  131. std:: cout << "protobuf decode time testing" << std:: endl;
  132. for ( int i = 0; i < TEST_COUNT; i++)
  133. {
  134. ProtobufDecode(strPb);
  135. }
  136. nowTime = getNowTime();
  137. std:: cout << "protobuf decode " << TEST_COUNT << " time, need time: "
  138. << nowTime - startTime << "ms" << std:: endl;
  139. #endif
  140. return 0;
  141. }
  • 使用下面的命令进行编译,编译时可能会警告,可以忽略
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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场