关于proto相关的知识可以参考系列博客 https://blog.csdn.net/daaikuaichuan/category_9869251.html
【xx.proto文件中如果要注释的话,注释符号也是双斜杠"//"】
重装protoc可参考 https://blog.csdn.net/u013498583/article/details/74231058
查看当前protoc版本: protoc --version
查看protoc安装位置:which protoc
查找protoc相关文件:sudo find / -name protoc
编译proto文件
protoc caffe.proto --cpp_out=./ 生成caffe.pb.h、caffe.pb.cc文件
protoc caffe.proto --python_out=./ 生成caffe_pb2.py文件
假如环境中装了一个以上的protoc版本,可使用protoc双击tab键,会弹出所有版本(protoc protoc_2.6.1
,其中protoc
是默认版本,protoc --version
是查看当前默认protoc版本)。
如果我们要使用非默认版本protoc编译proto,可以protoc_2.6.1 caffe.proto --cpp_out=./
这种方式编译。
在编译MobileNet-YOLO工程时,使用protoc protoc_2.6.1编译后的 caffe.pb.h
、caffe.pb.cc
文件粘贴到 caffe\include\caffe\proto
即可。
如果有一个proto_name
结构的 proto消息:
proto_name.set_xx(value)
该函数给对应的变量赋值
proto_name.add_xx()
该函数给对应的repeated变量增加一个xx类型的元素,返回值为指针类型。一般用法为:在for循环中调用一次,proto消息就添加一个该类型数据。
proto_name.mutable_xxx()
该函数返回指向该字段的一个指针,同时将该字段置为被设置状态。若该对象存在,则直接返回该对象,若不存在则新new 一个。
例如,有 proto_name.proto
文件,把这个 proto_name.proto
文件编译后,会产生 proto_name.pb.h
和 proto_name.pb.cc
, 生成的.h文件中的class都继承自::google::protobuf::Message
类,Message
类提供了一些方法可以检查或者操作整个message,包括:
bool IsInitialized() const;
检查是否所有required变量都已经初始化;
string DebugString() const;
返回message的可阅读的表示,主要用于调试程序;
void CopyFrom(const Person& from);
使用一个message的值覆盖本message;
void Clear();
清空message的所有成员变量值。
每个message类都提供了写入和读取message数据的方法,包括
bool SerializeToString(string* output) const;
// 把message编码进output。
bool ParseFromString(const string& data);
// 从string解码到message
bool SerializeToArray(char* buf,int size) const;
// 把message编码进数组buf.
bool ParseFromArray(const char* buf,int size);
// 把 buf解码到message。此解码方法效率较ParseFromString高很多,所以一般用这种方法解码。
bool SerializeToOstream(ostream* output) const;
// 把message编码进ostream
bool ParseFromIstream(istream* input);
// 从istream解码到message
备注:发送接收端所使用的加码解码方法不一定非得配对,即发送端用SerializeToString
接收端不一定非得用ParseFromString
,可以使用其他解码方法。
bool SerializeToString(string* output) const;
和 bool SerializeToArray(char* buf,int size) const;
例如当需要使用tcp/udp
发送包含数组的时候,建议使用 bool SerializeToArray(char* buf,int size) const;
,因为涉及到 数据包头、校验位之类的需要使用数组索引的方式,这样最好使用SerializeToArray
。
#define _fill_pack(name) \
{ \
char tbuf[256 * 1024]; \
uint32_t len = name.ByteSize(); \
auto header = name.header(); \
_fill_pack_head(static_cast<uint32_t>(netproxy_##name##_pack), len, \
reinterpret_cast<netproxy_pack_t *>(tbuf)); \
if (!name.SerializeToArray(&tbuf[sizeof(netproxy_pack_t)], len)) { \
SERROR << "serialize error, name:" << #name << " len:" << len; \
len = -sizeof(netproxy_pack_t); \
} \
Send_data(tbuf, len + sizeof(netproxy_pack_t), netproxy_##name##_pack); \
}
其他时候可以直接使用SerializeToString
。
当某个对外接口的消息结构 msgA
中的某个变量的结构总是要根据需求而频繁增删修改的时候,一般会在这个消息结构 msgA 中定义一个std::string proto_data
的变量,
这样只需要修改proto_name.proto
文件中的内容,而不需要总是修改 msgA
(尤其是当修改msgA
可能需要修改框架协议的时候,总之就是代价比较大),使用proto
方法来解决这个问题就特别方便。
原始结构:
struct msgA{
MsgHeader header;
bool is_ok;
structBBB msgbbb; //当这个结构体 structBBB 增删修改结构比较麻烦时,会造成频繁修改对外接口 msgA 的麻烦。
}
先定义一个 proto_name.proto
文件,把这个 proto_name.proto
文件编译后,会产生 proto_name.pb.h
和 proto_name.pb.cc
,
对其中定义的变量赋值是使用 set_xxx(111)
函数进行赋值,赋值完成后再序列化之后进行打包发送;
发送之前使用 proto_name.SerializeToString(&data)
把该 结构序列化为string类型,然后再发送。
原始结构 修改为:
struct msgA{
MsgHeader header;
bool is_ok;
//定义一个string类型的结构来代替 structBBB msgbbb; 然后把proto定义的数据结构 proto_name.proto赋值后,
//调用 proto_name.SerializeToString(proto_data) 进行序列化转换为string赋值给proto_data变量,这就是使用proto方式来解决。
std::string proto_data;
}
下面的函数,我们封装了proto自带的SerializeToString和ParseFromString方法
// msg转换成string
template <class T>
void MsgToString(const T msg, std::string &data) {
msg.SerializeToString(&data);
}
// 将string转换成msg
template <class T>
void StringToMsg(const std::string data, T &msg) {
msg.ParseFromString(data);
}
当我们需要使用配置文件来给许多变量进行赋值的时候,就可以把这些变量全都定义在 config.proto
中,然后建立一个config.pb.txt
文件,在config.pb.txt
文件中对config.proto
中的变量进行设置。
后续进行一些读取config.pb.txt
文件中的内容,然后需要的地方获取这些内容。
代码涉及到文件读取解析proto等等的内容,详解见下:
/**下面这函数是读取配置文件,然后将值赋给message,然后就可以对message以message.mutable_xxx()的形式进行获取数据了**/
/**env_fusion_data_是proto消息,继承于 google::protobuf::Message类**/
pinecone::common::GetProtoFromFile(data_file_name, &message);
/**定义见下**/
bool GetProtoFromFile(const std::string &file_name,
google::protobuf::Message *message) {
// Try the binary parser first if it's much likely a binary proto.
static const std::string kBinExt = ".bin";
if (std::equal(kBinExt.rbegin(), kBinExt.rend(), file_name.rbegin())) {
return GetProtoFromBinaryFile(file_name, message) ||
GetProtoFromASCIIFile(file_name, message);
}
return GetProtoFromASCIIFile(file_name, message) ||
GetProtoFromBinaryFile(file_name, message);
}
bool ParseFromIstream(istream* input)
bool GetProtoFromBinaryFile(const std::string &file_name,
google::protobuf::Message *message) {
std::fstream input(file_name, std::ios::in | std::ios::binary);
if (!input.good()) {
SERROR << "Failed to open file " << file_name << " in binary mode.";
return false;
}
if (!message->ParseFromIstream(&input)) {
SERROR << "Failed to parse file " << file_name << " as binary proto.";
return false;
}
return true;
}
ASCII
文件,调用了open()
,close()
和proto内置函数static bool Parse(io::ZeroCopyInputStream* input, Message* output);
bool GetProtoFromASCIIFile(const std::string &file_name,
google::protobuf::Message *message) {
using google::protobuf::TextFormat;
using google::protobuf::io::FileInputStream;
using google::protobuf::io::ZeroCopyInputStream;
int file_descriptor = open(file_name.c_str(), O_RDONLY);
if (file_descriptor < 0) {
SERROR << "Failed to open file " << file_name << " in text mode.";
// Failed to open;
return false;
}
ZeroCopyInputStream *input = new FileInputStream(file_descriptor);
bool success = TextFormat::Parse(input, message);
if (!success) {
SERROR << "Failed to parse file " << file_name << " as text proto.";
}
delete input;
close(file_descriptor);
return success;
}