基于C++的protobuf协议使用(四)项目应用与总结
五、项目应用
5.1 项目结构
plaintext
.
├── bin
├── build
├── CMakeLists.txt
├── include
├── lib
├── proto
│ ├── login.proto
│ └── student.proto
└── src└── test.cpp6 directories, 4 files
wy@ubuntu:~/testProto1$
5.2 示例源码
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)# 设置项目名称
project(test)# 查询 protobuf 编译器和库
find_package(Protobuf REQUIRED)# 判断查询是否成功
if(PROTOBUF_FOUND)message("protobuf found")
else()message(FATAL_ERROR "cannot find Protobuf")
endif()# 设置 protoc 的输出路径 /home/wy/testProto1/build/message
set(MESSAGE_DIR ${CMAKE_BINARY_DIR}/message)# 设置输出路径 *.pb.cc /home/wy/testProto1/src/message
set(PROTO_SRC ${PROJECT_SOURCE_DIR}/src/message)
# 设置输出路径 *.pb.h /home/wy/testProto1/include/message
set(PROTO_HDRS ${PROJECT_SOURCE_DIR}/include/message)if(EXISTS "${CMAKE_BINARY_DIR}/message" AND IS_DIRECTORY "${CMAKE_BINARY_DIR}/message")SET(DST_DIR ${MESSAGE_DIR})
else()file(MAKE_DIRECTORY ${MESSAGE_DIR})SET(DST_DIR ${MESSAGE_DIR})
endif()# 设置 protoc 的搜索路径 /home/wy/testProto1/proto
LIST(APPEND PROTO_FLAGS -I${CMAKE_SOURCE_DIR}/proto)# 迭代的获取 /home/wy/testProto1/proto/*.proto 目录下需要编译的 proto 文件
# 将 *.proto 文件存储到 MSG_PROTOS 集合中,注意 MSG_PROTOS 是我们自己定义的集合名
file(GLOB_RECURSE MSG_PROTOS ${CMAKE_SOURCE_DIR}/proto/*.proto)
# 迭代 MSG_PROTOS 集合
foreach(msg ${MSG_PROTOS})# 获取不带路径和扩展名的文件名,component 设置为 NAME_WEget_filename_component(PROTO_FILE_NAME ${msg} NAME_WE)# 定义两个变量set(MESSAGE_SRC "")set(MESSAGE_HDRS "")# 向列表变量名 MESSAGE_SRC 末尾添加数据# MESSAGE_SRC = "/home/wy/testProto1/build/message/login.pb.cc"# MESSAGE_HDRS = "/home/wy/testProto1/build/message/login.pb.h"list(APPEND MESSAGE_SRC "${PROJECT_BINARY_DIR}/message/${PROTO_FILE_NAME}.pb.cc")list(APPEND MESSAGE_HDRS "${PROJECT_BINARY_DIR}/message/${PROTO_FILE_NAME}.pb.h")# 调用 protoc 生成源码文 *.pb.cc 和 *.pb.h 文件# 生成的源码文件存储到 /home/wy/testProto1/build/message / 文件夹下,${DST_DIR}# ${PROTOBUF_PROTOC_EXECUTABLE} => /usr/bin/proto# ${PROTO_FLAGS} => -I${CMAKE_SOURCE_DIR}/proto => -I/home/wy/testProto1/proto# ${msg} => /home/wy/testProto1/proto/login.protoexecute_process(COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} ${PROTO_FLAGS} --cpp_out=${DST_DIR} ${msg})# ${MESSAGE_SRC} 文件拷贝到 ${PROTO_SRC}# 将 "/home/wy/testProto1/build/message/login.pb.cc"# 拷贝到 /home/wy/testProto1/src/message/file(COPY ${MESSAGE_SRC} DESTINATION ${PROTO_SRC})# 将 "/home/wy/testProto1/build/message/login.pb.h"# 拷贝到 /home/wy/testProto1/include/message/ 目录下file(COPY ${MESSAGE_HDRS} DESTINATION ${PROTO_HDRS})
endforeach()set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)include_directories(./include)
include_directories(${PROTO_HDRS})aux_source_directory(./src SRC_TEST)
aux_source_directory(${PROTO_SRC} SRC_PROTO)add_executable(test ${SRC_TEST} ${SRC_PROTO})target_include_directories(testPUBLIC${PROTOBUF_INCLUDE_DIRS}${CMAKE_CURRENT_BINARY_DIR}
)target_link_libraries(testPUBLIC${PROTOBUF_LIBRARIES}
)
5.3 执行 cmake .. && make
wy@ubuntu:~/testProto1$ cd build
wy@ubuntu:~/testProto1/build$ cmake .. && make
//..
-- Found Protobuf: /usr/lib/x86_64-linux-gnu/libprotobuf.so;-lpthread (found version "3.6.1")
protobuf found
-- Configuring done
-- Generating done
-- Build files have been written to: /home/wy/testProto1/build
Scanning dependencies of target test
[ 25%] Building CXX object CMakeFiles/test.dir/src/test.cpp.o
[ 50%] Building CXX object CMakeFiles/test.dir/src/message/login.pb.cc.o
[ 75%] Building CXX object CMakeFiles/test.dir/src/message/student.pb.cc.o
[100%] Linking CXX executable ../bin/test
[100%] Built target test
wy@ubuntu:~/testProto1/build$
查看项目结构
wy@ubuntu:~/testProto1$ tree
.
├── bin
│ └── test
├── build
│ └── message
│ ├── login.pb.cc
│ ├── login.pb.h
│ ├── student.pb.cc
│ └── student.pb.h
├── CMakeLists.txt
├── include
│ └── message
│ ├── login.pb.h
│ └── student.pb.h
├── lib
├── proto
│ ├── login.proto
│ └── student.proto
└── src├── message│ ├── login.pb.cc│ ├── student.pb.cc│ └── test.cpp19 directories, 45 files
wy@ubuntu:~/testProto1$
5.4 执行结果
wy@ubuntu:~/testProto1$ ./bin/test
serialization result: ...(二进制内容,输出可能乱码)
debugString:id: 2024001
name: "test"
email: "12345@qq.com"
phone {number: "13600009999"type: MOBILE
}
phone {number: "0913-8889999"type: HOME
}
-------------上面是序列化,下面是反序列化-------------
deserializedStudent debugString:id: 2024001
name: "test"
email: "12345@qq.com"
phone {number: "13600009999"type: MOBILE
}
phone {number: "0913-8889999"type: HOME
}
Student ID: 2024001
Name: test
E-mail address: 12345@qq.com
Mobile phone #: 13600009999
Home phone #: 0913-8889999
wy@ubuntu:~/testProto1$
六、总结
6.1 扩展 Protocol Buffers
无论或早或晚,在你发布出使用 Protocol Buffers 的代码后,你必定会想 “改进” Protocol Buffers 的定义,即我们自定义消息的 proto 文件。如果你想让新的 proto 文件向后兼容(backward-compatible),并且旧的 proto 文件能够向前兼容(forward-compatible),你一定希望如此,那么你在新的 proto 文件中就要遵守如下规则:
- 对已存在的任何字段,你都不能更改其标识(tag)号。
- 你绝对不能添加或删除任何
required
的字段。 - 你可以添加新的
optional
或repeated
的字段,但是你必须使用新的标识(tag)号(例如,在这个 proto 文件中从未使用过的标识号 —— 甚至于已经被删除过的字段使用过的标识号也不行)。
(有一些例外情况,但是它们很少使用。)
如果你遵守这些规则,老的代码将能很好地解析新的消息(message),并忽略掉任何新的字段。对老代码来说,已经被删除的 optional
字段将被赋予默认值,已被删除的 repeated
字段将是空的。新的代码也能够透明地读取旧的消息。但是,请牢记心中:新的 optional
字段将不会出现在旧的消息中,所以你要么需要显式地检查它们是否由 has_
前缀的函数置(set)了值,要么在你的 proto 文件中,在标识(tag)号的后面[default = value]
提供一个合理的默认值。如果没有为一个 optional
项指定默认值,那么就会使用与特定类型相关的默认值:对 string
来说,默认值是空字符串。对 boolean
来说,默认值是 false
。对数值类型来说,默认值是 0
。还要注意:如果你添加了一个新的 repeated
字段,你的新代码将无法告诉你它是否被留空了(被新代码),或者是否从未被置(set)值(被旧代码),这是因为它没有 has_
标志。
6.2 优化小技巧(Optimization Tips)
Protocol Buffers 的 C++ 库已经做了极度优化。但是,正确的使用方法仍然会提高很多性能。下面是一些小技巧,用来提升 Protocol Buffers 库的最后一丝速度能力:
- 如果有可能,重复利用消息(message)对象。即使被清除掉,消息(message)对象也会尽量保存所有被分配来重用的内存。这样的话,如果你正在处理很多类型相同的消息以及一系列相似的结构,有一个好办法就是重复使用同一个消息(message)对象,从而使内存分配的压力减小一些。然而,随着时间的流逝,对象占用的内存也有可能变得越来越大,尤其是当你的消息尺寸(译者注:各消息内容不同,有些消息内容多一些,有些消息内容少一些)不同的时候,或者你偶尔创建了一个比平常大很多的消息(message)的时候。你应该自己通过调用
SpaceUsed
函数监测消息(message)对象的大小,并在它太大的时候删除它。 - 在多线程中分配大量小对象的内存的时候,你的操作系统的内存分配器可能优化得不够好。在这种情况下,你可以尝试用一下 Google’s tcmalloc。
6.3 高级用法(Advanced Usage)
Protocol Buffers 的作用绝不仅仅是简单的数据存取以及序列化。请阅读 C++ API reference 全文来看看你还能用它来做什么。
protocol 消息类所提供的一个关键特性就是反射。你不需要编写针对一个特殊的消息(message)类型的代码,就可以遍历一个消息的字段,并操纵它们的值,就像 XML 和 JSON 一样。“反射” 的一个更高级的用法可能就是可以找出两个相同类型的消息之间的区别,或者开发某种 “协议消息的正则表达式”,利用正则表达式,你可以对某种消息内容进行匹配。只要你发挥你的想像力,就有可能将 Protocol Buffers 应用到一个更广泛的、你可能一开始就期望解决的问题范围上。
“反射” 是由 Message::Reflection interface
提供的。