ProtoBuf学习笔记

发布于:2025-07-08 ⋅ 阅读:(20) ⋅ 点赞:(0)


前言

protobuf(protocol buffer)是google 的一种数据交换的格式,它独立于平台语言。google 提供了protobuf多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。

Proto Buffer 是一种语言中立的、平台中立的、可扩展的序列化结构数据的方法。

Protocol Buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.

这是来自官网 Overview 页面对于 Protobuf 的简介,抛开繁杂的修饰词,Protobuf 的核心是序列化结构数据,为了更好地理解 Protobuf,我们首先需要知道序列化是什么。

序列化指的是将一个数据结构或者对象转换为某种能被跨平台识别的字节格式,以便进行跨平台存储或者网络传输。

例如前端和后端可能使用不同的编程语言,它们内部的数据表示方式可能不兼容。序列化提供了一种语言无关的格式来表示数据,这样不同的系统就可以理解和处理这些数据。在我们日常进行前后端开发时,后端通常会返回 JSON 格式的数据,前端拿到后再进行相关的渲染,JSON 其实就是序列化的一种表现形式。而我们这里提到的 Proto Buffer 就是序列化的其他表现形式。事实上除了 JSON 与 Protobuf 还有很多种形式,例如 XML、YAML。
由于它是一种二进制的格式,比使用 xm(20倍)、json(10倍)进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。

1. Protobuf的安装与使用

1.1 Windows平台下protobuf的安装

首先是protobuf的安装
官方下载地址https://github.com/protocolbuffers/protobuf/releases/tag/v3.5.0
这里是装在windows vs2019中使用,选择protobuf-cpp-3.5.0.zip文件下载
在这里插入图片描述

下载完成后解压
使用cmake来进行源码编译
编译配置如下:
在这里插入图片描述

要做的就是:选择并设置cmake对象,点击 configure,然后点击 generate,生成好后点击 open project
需要配置的参数包括:
protobuf_BUILD_SHARED_LIBS:需选中该选项,则可编译动态链接库
CMAKE_INSTALL_PREFIX:程序编译完成后安装的路径,默认在C盘,要求VS2019有管理员权限。
protobuf_WITH_ZLIB:取消选中该选项
generate生成如下,而后open project使用vs2019编译生成库文件
在这里插入图片描述
可以单独选择libprotobuf编译,也可以选择All Build编译 编译时可以选择debug和release,建议每一个都编译一遍,这样就有debug和release两个版本的库了。 我选择的是X64位的debug模式编译,生成的库在visualstudio目录下的Debug文件夹里。
在这里插入图片描述
可以看到我们生成了protobuf相关的lib库和dll库,一个是静态类型的,一个是动态类型的。
在这里插入图片描述
接下来我创建一个新的文件夹D:\protoc 然后在该文件夹内创建一个bin文件夹(用来存储刚才protobuf生成的库)和include文件夹(用来存储protobuf的头文件)
在这里插入图片描述

将libprotobufd.lib和libprotocd.lib, 以及protoc.exe拷贝到bin目录下,当然为了偷懒,可以将刚才Debug目录下的所有内容都拷贝到bin目录也可以。 将protobuf文件夹下src文件夹里的google文件夹及其内容拷贝到protoc的include文件夹

到此为止,我们protobuf的库生成工作就完成了。 因为我们要用到protoc命令,所以要将该命令配置到环境变量,在系统环境变量里添加一个环境变量PROTOBUF_HOME, 设置它的值为D:\cppsoft\protoc\bin
在这里插入图片描述

然后将该值添加到系统的path路径即可,格式为%PROTOBUF_HOME%
在这里插入图片描述

这样我们就可以直接使用protoc.exe了。

1.2 visual stuido配置protobuf

我们新建一个控制台项目,在项目属性中,配置选择Debug,平台选择X64,选择VC++目录, 在包含目录中添加 D:\cppsoft\protoc\include 在库目录中添加 D:\cppsoft\protoc\bin

在链接器的输入选项中添加protobuf用到的lib库

libprotobufd.lib
libprotocd.lib

到此,visual studio 的protobuf配置完毕。

1.3 生成pb文件

要想使用protobuf的序列化功能,需要生成pb文件,pb文件包含了我们要序列化的类信息。我们先创建一个msg.proto,该文件用来定义我们要发送的类信息

syntax = "proto3";
message Book
{
   string name = 1;
   int32 pages = 2;
   float price = 3;
}

这个文件定义了一个名为Book的消息类型,包含三个字段:name、pages和price。其中每个字段都有一个数字标识符,用于标识该字段在二进制流中的位置。
我们使用protoc.exe 基于msg.proto生成我们要用的C++类 在proto所在文件夹执行如下命令
在这里插入图片描述

值得注意的是,如果你安装了anaconda、caffe、pytorch,那么你的电脑里可能就会存在严重的protobuf版本冲突
所以不能直接使用

protoc --cpp_out=. ./msg.proto

因为你不知道会调用到哪个版本的protobuf

因此这里使用的时候指明路径

D:\protoc\bin\protoc.exe --cpp_out=. ./msg.proto

–cpp_out= 表示指定要生成的pb文件所在的位置 ./msg.proto 表示msg.proto所在的位置,因为我们是在msg.proto所在文件夹中执行的protoc命令,所以是当前路径即可。 执行后,会看到当前目录生成了msg.pb.h和msg.pb.cc两个文件,这两个文件就是我们要用到的头文件和cpp文件。 生成的pb.h文件中提供了protobuf实现的方法。
在这里插入图片描述

我们将这两个文件添加到项目里,然后在主函数中包含msg.pb.h,做如下测试

#include <iostream>
#include "msg.pb.h"
int main()
{
    Book book;
    book.set_name("CPP programing");
    book.set_pages(100);
    book.set_price(200);
    std::string bookstr;
    book.SerializeToString(&bookstr);
    std::cout << "serialize str is " << bookstr << std::endl;
    Book book2;
    book2.ParseFromString(bookstr);
    std::cout << "book2 name is " << book2.name() << " price is " 
        << book2.price() << " pages is " << book2.pages() << std::endl;
    getchar();
}

上面的demo中将book对象先序列化为字符串,再将字符串反序列化为book2对象。 这样就是visual studio 配置和使用protobuf的方法。

1.4 在网络中的应用

先为服务器定义一个用来通信的proto

syntax = "proto3";
message MsgData
{
   int32  id = 1;
   string data = 2;
}

id代表消息id,data代表消息内容 我们用protoc生成对应的pb.h和pb.cc文件 将proto,pb.cc,pb.h三个文件复制到我们之前的服务器项目里并且配置。

我们修改服务器接收数据和发送数据的逻辑 当服务器收到数据后,完成切包处理后,将信息反序列化为具体要使用的结构,打印相关的信息,然后再发送给客户端

    MsgData msgdata;
    std::string receive_data;
    msgdata.ParseFromString(std::string(_recv_msg_node->_data, _recv_msg_node->_total_len));
    std::cout << "recevie msg id  is " << msgdata.id() << " msg data is " << msgdata.data() << endl;
    std::string return_str = "server has received msg, msg data is " + msgdata.data();
    MsgData msgreturn;
    msgreturn.set_id(msgdata.id());
    msgreturn.set_data(return_str);
    msgreturn.SerializeToString(&return_str);
    Send(return_str);

同样的道理,客户端在发送的时候也利用protobuf进行消息的序列化,然后发给服务器

    MsgData msgdata;
    msgdata.set_id(1001);
    msgdata.set_data("hello world");
    std::string request;
    msgdata.SerializeToString(&request);

2. protobuf语法

Protocol Buffers (protobuf) 的核心是一个用于定义结构化数据(消息)的强类型接口描述语言 (IDL)。它的语法简洁清晰,主要写在 .proto 文件中。以下是 protobuf 语法的关键组成部分和规则:

2.1 文件结构

  • 文件开头: 通常指定 protobuf 的语法版本。

    syntax = "proto3"; // 使用 proto3 语法(当前推荐版本)。
    //proto2 也是有效的,但 proto3 更简洁且是未来方向。
    
  • 包声明 (可选但推荐): 用于防止消息类型之间的命名冲突,对应生成的代码中的命名空间/包。

    package mypackage;
    
  • 导入 (可选): 引入其他 .proto 文件中的定义。

    import "other_protos.proto";
    import public "public_dependency.proto"; // 公开导入,使导入当前文件的其他文件也能访问 public_dependency.proto 中的定义
    
  • 选项 (可选): 设置文件级、消息级、字段级或服务级的特定选项,可以影响代码生成或运行时行为。

    option java_package = "com.example.generated"; // 示例:指定生成的 Java 类的包名
    option optimize_for = SPEED; // 优化选项 (SPEED, CODE_SIZE, LITE_RUNTIME)
    
  • 消息定义 (Message): 核心部分,定义一个结构化数据类型。

  • 服务定义 (Service) (可选)定义 RPC (远程过程调用) 接口。

  • 枚举定义 (Enum) (可选): 定义一组命名的常量。

2.2 消息定义 (message)

消息是 protobuf 中数据的主要载体,类似于结构体、类或记录。

message SearchRequest {
  // 字段定义在这里
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 字段规则 (Field Rules - Proto3):

    • singular: (默认) 字段可以有零个或一个值(但序列化时无法区分未设置值和默认值)。proto3 中不显式写规则时就是 singular。

    • repeated: 字段可以重复任意次(包括零次)。顺序会被保留。相当于列表或数组。

    • optional: (Proto3.15+) 显式声明一个字段是可选的,允许检测字段是否被显式设置(区分未设置和默认值)。需要启用 optional 支持。

    message ExampleMessage {
      string required_field = 1;      // singular (默认)
      repeated int32 numbers = 2;     // repeated (列表)
      optional string comment = 3;    // optional (显式可选,需要版本支持)
    }
    
  • 字段类型 (Field Type):

    • 标量类型 (Scalar): 基本数据类型。
.proto Type C++ Type Java Type Python Type Go Type Notes
double double double float float64
float float float float float32
int32 int32 int int int32 变长编码。负数效率低,用 sint32 代替。
int64 int64 long int/long int64 变长编码。负数效率低,用 sint64 代替。
uint32 uint32 int int/long uint32 变长编码。无符号整数。
uint64 uint64 long int/long uint64 变长编码。无符号整数。
sint32 int32 int int int32 变长编码。有符号整数,负数编码效率高。
sint64 int64 long int/long int64 变长编码。有符号整数,负数编码效率高。
fixed32 uint32 int int/long uint32 固定 4 字节。值 > 2^28 时比 uint32 高效。
fixed64 uint64 long int/long uint64 固定 8 字节。值 > 2^56 时比 uint64 高效。
sfixed32 int32 int int int32 固定 4 字节。
sfixed64 int64 long int/long int64 固定 8 字节。
bool bool boolean bool bool
string string String str/unicode string UTF-8 或 7-bit ASCII 文本。长度 <= 2^32。
bytes string ByteString bytes []byte 任意二进制数据。长度 <= 2^32。
  • 复合类型:

    • 其他消息类型: 直接使用定义好的 message 名称。
    message Result {
      string url = 1;
      string title = 2;
    }
    message SearchResponse {
      repeated Result results = 1; // 使用 Result 消息类型作为字段类型
    }
    
    • 枚举类型: 使用定义好的 enum 名称。
  • 字段名称: 使用蛇形命名法 (snake_case),如 field_name, another_field。

  • 字段编号 (Field Number): 这是最关键的部分!

    • 每个字段在消息定义中必须有一个唯一的整数编号 (1, 2, 3, …)。

    • 编号范围:1 到 536, 870, 911 (2^29 - 1)。

    • 编号 1 到 15:占用 1 个字节的 tag (字段编号 + 类型信息)。对于高频出现或可选的字段,优先使用 1-15 的编号以节省空间。

    • 编号 16 到 2047:占用 2 个字节。

    • 编号 >= 2048:占用更多字节(但 tag 本身最多占用 5 个字节)。

    • 一旦消息类型被使用,其字段编号就不可更改! 修改编号会使现有序列化数据与新定义不兼容。

  • 默认值 (Default Values - Proto3):

    • 当解析消息时,如果序列化数据中没有某个 singular 或 optional 字段,解析后的对象中该字段会被设置为该类型的默认值:

      • string: 空字符串 (“”)

      • bytes: 空字节序列

      • bool: false

      • 数值类型 (int32, float, double 等): 0

      • enum: 定义的第一个枚举值(其编号必须为 0)。

      • message 字段: 通常是该语言对应的“空”或 null 值(具体取决于语言实现)。注意: 不会递归创建内部消息的默认实例。

    • repeated 字段默认为空列表。

2.3 枚举定义 (enum)

定义一组命名的数值常量。

enum Corpus {
  CORPUS_UNSPECIFIED = 0; // 枚举定义中必须有一个值为 0 的常量!它通常作为默认值或表示未指定。
  CORPUS_UNIVERSAL = 1;
  CORPUS_WEB = 2;
  CORPUS_IMAGES = 3;
  CORPUS_LOCAL = 4;
  CORPUS_NEWS = 5;
  CORPUS_PRODUCTS = 6;
  CORPUS_VIDEO = 7;
}
  • 常量名称: 使用大写的蛇形命名法 (UPPER_SNAKE_CASE)。

  • 常量值: 必须是整数。允许负数,但不推荐。

  • 别名 (Alias): 使用 option allow_alias = true; 可以允许多个枚举常量具有相同的数值。

enum EnumWithAlias {
  option allow_alias = true;
  EWA_UNSPECIFIED = 0;
  EWA_STARTED = 1;
  EWA_RUNNING = 1; // EWA_STARTED 的别名
}
  • 保留值 (Reserved Values): 与消息字段类似,可以保留枚举值或名称,防止未来被意外使用。参见下节。

2.4 保留字段和保留值 (reserved)

为了防止在更新 .proto 文件时意外重用已删除的字段编号或名称(这会导致严重的兼容性问题),可以将它们标记为 reserved。

  • 保留字段编号:
message Foo {
  reserved 2, 15, 9 to 11; // 保留字段编号 2, 15, 9, 10, 11
  reserved "bar", "baz";    // 保留字段名称 "bar", "baz"
  // string bar = 2;       // 编译错误!编号 2 和名称 "bar" 都被保留了。
  int32 new_field = 12;     // 这是允许的
}
  • 保留枚举值 (类似):
enum ReservedEnum {
  reserved 2, 15, 9 to 11;
  reserved "BAR", "BAZ";
  RE_UNSPECIFIED = 0;
  RE_FOO = 1;
  // RE_BAR = 2; // 错误!编号 2 和名称 "BAR" 都被保留了。
}
  • 重要: reserved 列表不能混合字段编号和名称在同一行,但可以在同一个 reserved 语句中用分号分隔或用多个 reserved 语句。

2.5 嵌套类型

可以在 message 内部定义其他 message 或 enum。

message Outer {
  message Inner {
    string inner_field = 1;
  }
  enum NestedEnum {
    NESTED_ENUM_UNSPECIFIED = 0;
    VALUE_A = 1;
  }
  Inner inner_msg = 1;
  NestedEnum my_enum = 2;
}
  • 在外部消息之外访问嵌套类型:Outer.Inner, Outer.NestedEnum。

2.6 oneof (互斥字段)

一组字段中,在同一时间最多只有一个字段可以被设置。类似于 C/C++ 的 union,但更安全且提供额外信息(知道是哪个字段被设置了)。

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    int32 id = 5;
    NestedMessage nested_msg = 6;
  }
}
  • 特性:

    • 设置 oneof 中的任何一个字段会自动清除所有其他成员。

    • 如果解析器在 wire 上遇到同一个 oneof 的多个成员,只有最后一个看到的成员会被使用。

    • oneof 不能是 repeated。

    • 在 proto3 中,不能直接使用 optional(但可以通过 oneof 实现类似效果)。

    • 向后兼容性:添加或删除 oneof 字段需要小心处理。

2.7 map (映射类型)

定义键值对映射。

map<KeyType, ValueType> map_field = N;
  • 键类型 (KeyType): 只能是整数类型或 string(任何标量类型除了 float, double, bytes, enum 和 message)。

  • 值类型 (ValueType): 可以是除了另一个 map 之外的任何类型。

  • 特性:

    • map 字段不能是 repeated。

    • Wire 格式排序和 map 迭代排序是未定义的,不能依赖特定顺序。

    • 生成文本格式 (.proto) 时,map 按键排序。数值键按数值排序。

    • 解析时遇到重复的键,最后一个键有效。

    • 向后兼容性:map 在 wire 上等同于 repeated 一个包含 key 和 value 字段的消息。许多 protobuf 实现提供了向后兼容的辅助函数。

2.8 服务定义 (service - 用于 gRPC)

定义 RPC 服务接口。

service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse); // 一个简单的 RPC
  rpc ServerStreamingMethod(StreamRequest) returns (stream StreamResponse); // 服务器流式 RPC
  rpc ClientStreamingMethod(stream StreamRequest) returns (StreamResponse); // 客户端流式 RPC
  rpc BidirectionalStreamingMethod(stream StreamRequest) returns (stream StreamResponse); // 双向流式 RPC
}
  • rpc: 关键字,定义一个远程过程调用。

  • Search(SearchRequest) returns (SearchResponse): 定义一个名为 Search 的方法,它接受一个 SearchRequest 消息作为参数,并返回一个 SearchResponse 消息。

  • 流式 RPC:

    • stream 关键字放在参数类型前 (stream StreamRequest) 表示客户端发送一个消息流。

    • stream 关键字放在返回类型前 (returns (stream StreamResponse)) 表示服务器返回一个消息流。

    • 双向流式 (stream 在参数和返回类型前都有) 表示客户端发送一个流,服务器也返回一个流。

2.9 注释

  • //:单行注释。

  • /* … */:多行注释。

2.10 更新消息类型的规则 (向后兼容性)

  • 可以安全进行的更改 (不会破坏现有序列化数据的解析):

    • 向消息添加新的 optional 或 repeated 字段(使用新的字段编号!)。

    • 删除一个 optional 或 repeated 字段(最好先标记为 reserved)。

    • 将字段从一个 oneof 移动到另一个 oneof(需要小心,某些语言实现可能有限制)。

    • 更改字段类型,只要新类型在 wire 格式上是兼容的(例如 int32 到 int64, uint32, sint32;string 到 bytes)。需要仔细查阅类型兼容性表。

    • 重命名字段(但最好保留旧名称的 JSON 别名 json_name 选项,或者直接使用 reserved 并添加新字段)。

    • 在枚举中添加新的常量(确保旧代码能处理新值,通常默认值 0 不变)。

  • 会破坏兼容性的更改 (必须避免!):

    • 更改现有字段的编号。

    • 更改字段的类型为不兼容的类型(例如 int32 到 string)。

    • 更改字段的名称而不做处理(影响 JSON 序列化和某些工具)。

    • 将 singular 字段改为 repeated 或反之(除非使用 packed 且类型兼容,但通常很危险)。

    • 删除或重命名枚举常量(除非标记为 reserved)。

    • 更改默认值(影响新代码解析旧数据)。

    • 将字段添加进现有的 oneof 或从其中移除。

  • 最佳实践:

    • 总是为新字段使用新的、唯一的字段编号。

    • 删除字段时,立即将其编号和名称标记为 reserved。

    • 优先使用 optional(proto3.15+)或 oneof 来显式表示可选字段,以区分未设置和默认值。

    • 仔细规划枚举,确保第一个值(编号 0)表示“未指定”或“未知”。

总结: Protobuf 语法专注于定义数据结构 (message, enum) 和 RPC 服务 (service)。其核心在于为每个字段指定唯一的编号和类型。理解字段规则 (singular, repeated, optional)、默认值、reserved 机制以及更新规则对于设计健壮且向后兼容的接口至关重要。.proto 文件是不同系统间交换数据的契约。

3. 实例演示

syntax = "proto3"; // 声明了protobuf的版本

package fixbug; // 声明了代码所在的包(对于C++来说是namespace)

// 定义下面的选项,表示生成service服务类和rpc方法描述,默认不生成
option cc_generic_services = true;

message ResultCode
{
    int32 errcode = 1;
    bytes errmsg = 2;
}

// 数据   列表   映射表
// 定义登录请求消息类型  name   pwd
message LoginRequest
{
    bytes name = 1;
    bytes pwd = 2;
}

// 定义登录响应消息类型
message LoginResponse
{
    ResultCode result = 1;
    bool success = 2;
}

message GetFriendListsRequest
{
    uint32 userid = 1;
}

message User
{
    bytes name = 1;
    uint32 age = 2;
    enum Sex
    {
        MAN = 0;
        WOMAN = 1;
    }
    Sex sex = 3;
}

message GetFriendListsResponse
{
    ResultCode result = 1;
    repeated User friend_list = 2;  // 定义了一个列表类型
}

// 在protobuf里面怎么定义描述rpc方法的类型 - service
service UserServiceRpc
{
    rpc Login(LoginRequest) returns(LoginResponse);
    rpc GetFriendLists(GetFriendListsRequest) returns(GetFriendListsResponse);
}

生成的pb.cc与pb.h文件则是创建数据的方法实现

main.c文件

#include "test.pb.h"
#include <iostream>
#include <string>
using namespace fixbug;

int main()
{
    // LoginResponse rsp;
    // ResultCode *rc = rsp.mutable_result();
    // rc->set_errcode(1);
    // rc->set_errmsg("登录处理失败了");
    
    GetFriendListsResponse rsp;
    ResultCode *rc = rsp.mutable_result();
    rc->set_errcode(0);

    User *user1 = rsp.add_friend_list();
    user1->set_name("zhang san");
    user1->set_age(20);
    user1->set_sex(User::MAN);

    User *user2 = rsp.add_friend_list();
    user2->set_name("li si");
    user2->set_age(22);
    user2->set_sex(User::MAN);

    std::cout << rsp.friend_list_size() << std::endl;

    return 0;
}

int main1()
{
    // 封装了login请求对象的数据
    LoginRequest req;
    req.set_name("zhang san");
    req.set_pwd("123456");

    // 对象数据序列化 =》 char*
    std::string send_str;
    if (req.SerializeToString(&send_str))
    {
        std::cout << send_str.c_str() << std::endl;
    }

    // 从send_str反序列化一个login请求对象
    LoginRequest reqB;
    if (reqB.ParseFromString(send_str))
    {
        std::cout << reqB.name() << std::endl;
        std::cout << reqB.pwd() << std::endl;
    }

    return 0;
}

主要提供读方法与写方法

message LoginRequest 即是 class LoginRequest

class LoginRequest : public ::google::protobuf::Message

name()//读成员变量
pwd()
set_name(xxx)//写成员变量
set_pwd(xxx)

SerializeToString//序列化方法
ParseFromString//反序列化方法
// 在protobuf里面怎么定义描述rpc方法的类型 - service
service UserServiceRpc
{
    rpc Login(LoginRequest) returns(LoginResponse);
    rpc GetFriendLists(GetFriendListsRequest) returns(GetFriendListsResponse);
}

主要生成方法
Callee ServiceProvider rpc服务提供者
class UserServiceRpc : public google::protobuf::Service

  virtual void Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::fixbug::LoginRequest* request,
                       ::fixbug::LoginResponse* response,
                       ::google::protobuf::Closure* done);
  virtual void GetFriendLists(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::fixbug::GetFriendListsRequest* request,
                       ::fixbug::GetFriendListsResponse* response,
                       ::google::protobuf::Closure* done);

  // implements Service ----------------------------------------------

  const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* GetDescriptor();

Caller ServiceConsumer rpc服务消费者
class UserServiceRpc Stub :public UserServiceRpc

  UserServiceRpc_Stub(::PROTOBUF_NAMESPACE_ID::RpcChannel* channel);
  
  void Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::fixbug::LoginRequest* request,
                       ::fixbug::LoginResponse* response,
                       ::google::protobuf::Closure* done);
  void GetFriendLists(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::fixbug::GetFriendListsRequest* request,
                       ::fixbug::GetFriendListsResponse* response,
                       ::google::protobuf::Closure* done);

::PROTOBUF_NAMESPACE ID::RpcChannel* channel_;

此处Login方法与GetFriendLists方法实际上是通过channel的调用,如下:

void UserServiceRpc_Stub::Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                              const ::fixbug::LoginRequest* request,
                              ::fixbug::LoginResponse* response,
                              ::google::protobuf::Closure* done) {
  channel_->CallMethod(descriptor()->method(0),
                       controller, request, response, done);
}
void UserServiceRpc_Stub::GetFriendLists(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                              const ::fixbug::GetFriendListsRequest* request,
                              ::fixbug::GetFriendListsResponse* response,
                              ::google::protobuf::Closure* done) {
  channel_->CallMethod(descriptor()->method(1),
                       controller, request, response, done);
}

RpcChannel是一个抽象类,如下:

class PROTOBUF_EXPORT RpcChannel{
public:
inline Rpcchannel(){};
virtual ~Rpcchannel();

virtual void CallMethod(const MethodDescriptor* method,
						RpcController* contnoller, const Message* request,
						Message* response, Closure*done)=0;

private:
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Rpcchannel);

那么在rpc_stub类初始化时,把自定义重写的派生类MyRpcChannel 传入rpc_stub的channel中,即可在其中进行rpc方法的序列化,发起远程rpc方法的调用请求

class MyRpcChannel : public RpcChannel
{
	virtual void CallMethod(const MethodDescriptor* method,
							RpcController* contnoller, const Message* request,
							Message* response, Closure*done)

	{
		xxxxxxxxxxxxxxxx
	}
}

4. 写在后面的话

最开始直接安装最新版的Protobuf
点击生成后产生如下问题:
在这里插入图片描述
查阅得知是没有安装abseil-cpp库,需要先按照abseil库
安装好后cmake仍然报错,绷不住了
在这里插入图片描述
又查找选择另一个版本,但是这次安装时提示需要几个第三方库(abseil-cpp、googletest和jsoncpp)的源码,我们需要去它们自己的源码库里面下载解压到对应的文件夹里面。
于是我去github上下载对应的cpp源文件放在third_party目录下
在这里插入图片描述
这次编译直接报出c++标准不同的问题,也许调整编译器的默认c++版本可以
在这里插入图片描述

于是继续降版本
找到黑马程序员的视频,跟着他降到2017年 11月16日发布的版本才能正常编译,绷不住了
还是这些培训班的搞的一清二楚,以后编译一定要先找个培训班视频看看再动手


网站公告

今日签到

点亮在社区的每一天
去签到