protobuf入门实践1
下载和安装
protobuf:https://github.com/google/protobuf
解压压缩包:unzip protobuf-master.zip
2、进入解压后的文件夹:cd protobuf-master
3、安装所需工具:sudo apt-get install autoconf automake libtool curl make g++ unzip
4、自动生成configure配置文件:./autogen.sh
5、配置环境:./configure
6、编译源代码(时间比较长):make
完成之后输入protoc,如下输出即安装成功
proto配置文件
syntax = "proto3"; //声明protobuf的版本package fixbug; //声明了代码所在的包 (对于C++来说就是namespace)//定义登录请求消息类型 name pwd
message LoginRequest{bytes name = 1; //等于1表示这是第一个参数,一般string的存储定义为bytesbytes pwd = 2;
}//定义登录响应消息类型
message LoginResponse{int32 errcode = 1;bytes errmsg = 2;bool success = 3;
}
xxx.proto文件定义了protobuf的版本,更重要的是定义了用户后面需要序列和反序列化的自定义消息类型,这会当做后面的远程rpc调用的参数类型。
message是protobuf内置的抽象类message,用于定义远程rpc传输的各种消息类型,语法是:
message{数据类型 变量名 = index}, 数据类型既可以是内置的基本的数据类型也可以是其他message类型。
上述简单定义了一个登陆所需要的请求消息以及响应消息。
protobuf支持多种语言,可以将上述proto配置文件编译成所支持的任意语言,例如c++、java等等
下面通过protoc命令编译为c++版本
输入protoc xxx.proto --cpp_out=./
, 表示在当前目录下生产proto配置文件所对应用户端可以使用的文件(C++源文件)如下:
如何使用这些源文件?
#include "test.pb.h"
#include <iostream>
#include <string>using namespace fixbug;int main(){ //封装login请求对象的数据LoginRequest req;req.set_name("zhang san");req.set_pwd("123456");//将LoginRequest对象序列化成字节数组(char*)std::string send_str;if(req.SerializeToString(&send_str)){std::cout<< send_str.c_str() << "\n";}//从send_str反序列化出一个login请求对象LoginRequest reqB;if(reqB.ParseFromString(send_str)){std::cout<<"name:"<<reqB.name()<<"\n"<<"pwd:"<<reqB.pwd()<<"\n";}return 0;
}
执行g++ main.cc test.pb.cc -lprotobuf && ./a.out
应该看起来还挺简单的,需要注意的是ResultCode变量的获取是,fixbug就是namespace fixbug, 然后每个消息类型对应在xxx.pb.cc文件中就是再fixbug命名空间下的一个类,消息类型里面定义的参数类型就是类里面的成员变量,并提供了这些成员变量的set_xxx(),xxx()方法来用于设置这些成员变量和获取该变量。是不是这样呢?看看生成的xxx.pb.h类吧:
namespace fixbug {
class GetFriendsListRequest;
class GetFriendsListRequestDefaultTypeInternal;
extern GetFriendsListRequestDefaultTypeInternal _GetFriendsListRequest_default_instance_;
class GetFriendsListResponse;
class GetFriendsListResponseDefaultTypeInternal;
extern GetFriendsListResponseDefaultTypeInternal _GetFriendsListResponse_default_instance_;
class LoginRequest;
class LoginRequestDefaultTypeInternal;
extern LoginRequestDefaultTypeInternal _LoginRequest_default_instance_;
class LoginResponse;
class LoginResponseDefaultTypeInternal;
extern LoginResponseDefaultTypeInternal _LoginResponse_default_instance_;
class ResultCode;
class ResultCodeDefaultTypeInternal;
extern ResultCodeDefaultTypeInternal _ResultCode_default_instance_;
class User;
class UserDefaultTypeInternal;
extern UserDefaultTypeInternal _User_default_instance_;
} // namespace fixbug
可以很清楚的看到,的确是这样,定义的消息数据类型都是一个个类,那么看看具体的一个LoginRequest类:
class LoginRequest :public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:fixbug.LoginRequest) */ {public:LoginRequest();virtual ~LoginRequest();LoginRequest(const LoginRequest& from);LoginRequest(LoginRequest&& from) noexcept: LoginRequest() {*this = ::std::move(from);}...........// bytes name = 1;void clear_name();const std::string& name() const;void set_name(const std::string& value);void set_name(std::string&& value);void set_name(const char* value);void set_name(const void* value, size_t size);std::string* mutable_name();std::string* release_name();void set_allocated_name(std::string* name);private:const std::string& _internal_name() const;void _internal_set_name(const std::string& value);std::string* _internal_mutable_name();public:// bytes pwd = 2;void clear_pwd();const std::string& pwd() const;void set_pwd(const std::string& value);void set_pwd(std::string&& value);void set_pwd(const char* value);void set_pwd(const void* value, size_t size);std::string* mutable_pwd();std::string* release_pwd();void set_allocated_pwd(std::string* pwd);private:const std::string& _internal_pwd() const;void _internal_set_pwd(const std::string& value);std::string* _internal_mutable_pwd();private:class _Internal;::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArena _internal_metadata_;::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr pwd_;mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;friend struct ::TableStruct_test_2eproto;
.....
};
可以看到的确如我们所说。
再来修改一下proto文件,介绍一下列表的使用,因为在参数调用过程中,要么就是单个数据,要么就是该数据组成的列表,当然还有映射类型(有兴趣可以自行了解)
syntax = "proto3"; //声明protobuf的版本package fixbug; //声明了代码所在的包 (对于C++来说就是namespace)message ResultCode{ //定义返回的错误码int32 errcode = 1;bytes errmsg = 2;
}//定义登录请求消息类型 name pwd
message LoginRequest{bytes name = 1; //等于1表示这是第一个参数,一般string的存储定义为bytesbytes pwd = 2;
}//定义登录响应消息类型
message LoginResponse{ResultCode result = 1;bool success = 3;
}
message GetFriendsListRequest{uint32 user_id = 1;
}message User{bytes name = 1;uint32 age = 2;enum Sex{MAN = 0;WOMAN = 1;}Sex sex = 3;
}message GetFriendsListResponse{ResultCode result = 1;repeated User friend_list = 2; //定义了一个列表数据类型
}
这里新增了三个消息类型,分别是User、GetFriendsListRequest、GetFriendsListResponse, 在GetFriendsListResponse消息类型中repeated 关键字是定义多个User,即一个User列表,这里需要注意的是为了避免代码重复,将错误码errcode和错误消息errmsg抽象成一个单独的消息类型。
老样子:protoc xxx.proto --cpp_out=./
#include "test.pb.h"
#include <iostream>
#include <string>using namespace fixbug;int main(){// LoginResponse rsp;// ResultCode* res = rsp.mutable_result();// rsp.set_success(0);// res->set_errcode(1);// res->set_errmsg("login failed");GetFriendsListResponse list; //列表操作ResultCode* rc = list.mutable_result();rc->set_errcode(0);rc->set_errmsg("");//添加用户User* u1 = list.add_friend_list();u1->set_name("zs");u1->set_age(21);u1->set_sex(User::MAN);User* u2 = list.add_friend_list();u2->set_name("ls");u2->set_age(21);u2->set_sex(User::MAN);std::cout<<"list size = "<<list.friend_list_size()<<"\n";for(int i = 0; i<list.friend_list_size(); i++){User u = list.friend_list(i);std::cout<<"name : "<< u.name()<<" ";std::cout<<"age : "<<u.age()<<" ";std::cout<<"sex : "<<u.sex()<<"\n";}return 0;
}
错误码和错误消息由于也封装成了一个消息,这里获取是通过::mutable_result()方法返回一个该变量的指针,对该指针的修改就行对原ResultCode对象类型的赋值操作。后面就是列表的操作,通过add_friend_list()方法返回一个需要新添加的User,friend_list_size()用于获取列表的长度,friend_list()用于得到列表中某个索引的User对象。
执行结果:
list size = 2
name : zs age : 21 sex : 0
name : ls age : 21 sex : 0