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
syntax = "proto3"; //声明protobuf的版本
package fixbug; //声明了代码所在的包 (对于C++来说就是namespace)
//定义登录请求消息类型 name pwd
message LoginRequest{
bytes name = 1; //等于1表示这是第一个参数,一般string的存储定义为bytes
bytes 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
#include
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的存储定义为bytes
bytes 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
#include
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