protobuf简介
Protocol Buffers(简称 protobuf)是 Google 开发的一种数据交换格式。它是一种灵活、高效且自动化的结构化数据序列化方法,类似于 XML、JSON 和其他配置文件格式,但更小、更快、更简单。我们的逻辑是有类等抽象数据构成的,而tcp是面向字节流的,我们需要将类结构序列化为字符串来传输。
主要特点:
- 语言无关:protobuf 支持多种编程语言,包括 C++、Java、Python 等,并且可以轻松地在不同语言之间进行通信。
- 平台无关:可以跨多个平台使用,无论是在 32 位还是 64 位系统上。
- 效率高:相比于 XML 或 JSON,protobuf 在序列化和反序列化时的性能更好,生成的数据也更紧凑。
编译protobuf
如果是linux系统,可以直接从自己的包管理器下载protobuf
例如archlinux
如果是windows系统,我们需要从官网 下载源代码进行编译
具体教程
当protobuf编译完成后,我们可以通过protobuf --version
来检查是否安装成功
使用流程
- 定义消息格式:首先需要定义消息的结构,这通常是通过 .proto 文件完成的。这些文件描述了你想要交换的数据的结构。
我们先创建一个msg.proto
文件,并且写入如下内容
1 2 3 4 5 6 7
| syntax = "proto3"; message Book { string name = 1; int32 pages = 2; float price = 3; }
|
这个文件用来定义我们需要发送的信息
- 编译 .proto 文件:使用 Protocol Buffers 编译器(protoc),根据 .proto 文件生成特定语言的源代码
1
| protoc --cpp_out=. ./msg.proto
|
./msg.proto 表示msg.proto所在的位置,因为我们是在msg.proto所在文件夹中执行的protoc命令,所以是当前路径即可。
执行后,会看到当前目录生成了msg.pb.h和msg.pb.cc两个文件,这两个文件就是我们要用到的头文件和cpp文件。
- 序列化与反序列化:使用生成的类来创建消息对象,并将这些对象序列化为字节流,或者从字节流中反序列化为对象。
这里我们写一个测试函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void test_protobuf() { 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();
}
|
输出如下:
1 2 3
| serialize str is CPP programingdHC book2 name is CPP programing price is 200 pages is 100
|
上面的demo中将book对象先序列化为字符串,再将字符串反序列化为book2对象。
在网络中的应用
先为服务器定义一个用来通信的protobuf
1 2 3 4 5 6
| syntax = "proto3"; message MsgData { int32 id = 1; string data = 2; }
|
id代表消息的编号,data代表消息的内容
接着修改服务器接收和发送数据的逻辑
当服务器收到数据并完成切包处理,将信息反序列化为具体要使用的结构,打印相关信息,然后再发送给客户端
1 2 3 4 5 6 7 8 9 10 11 12 13
| Data msgdata; std::string receive_data; msgdata.ParseFromString( std::string( _recv_msg_node->_msg, _recv_msg_node->_total_len ) ); std::cout << "Recv msg is: " << msgdata.data() << std::endl; std::string return_str = "server has receive msg, msg data is " + msgdata.data(); Data msgreturn; msgreturn.set_id( msgdata.id() ); msgreturn.set_data( msgdata.data() ); msgreturn.SerializeToString( &return_str ); Send( return_str );
|
同样,客户端在发送的时候也利用protobu进行消息序列化,然后发送给服务器
1 2 3 4 5 6 7 8 9 10
| Data msgdata; msgdata.set_id( 1001 ); msgdata.set_data( "Hello world!" ); std::string request; msgdata.SerializeToString( &request ); std::cout << "message id: " << msgdata.id() << " content: " << msgdata.data() << std::endl; boost::asio::write( sock, boost::asio::buffer( request, request.length() ) );
|
关于protbuf在CMake中的配置
注意一定要在编译参数中加入-Wl,--copy-dt-needed-entries
,否则会报DSO missing
1 2 3 4
| set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O2 -Wall -Wextra -Weffc++ -Werror=uninitialized -Werror=return-type -Wconversion -Wsign-compare -Werror=unused-result -Werror=suggest-override -Wzero-as-null-pointer-constant -Wmissing-declarations -Wold-style-cast -Wnon-virtual-dtor -Wl,--copy-dt-needed-entries" )
|
关于对proto文件的配置,可以参考这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| find_program( PROTOC_CXX protoc DOC "Protobuf Compiler (protoc)" REQUIRED )
file (GLOB PROTO_SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.proto" )
set(PROTO_PATH "${CMAKE_CURRENT_SOURCE_DIR}") set(PROTO_CXX_OUT "${CMAKE_CURRENT_BINARY_DIR}/gen_cxx")
file(MAKE_DIRECTORY ${PROTO_CXX_OUT})
foreach(input_proto ${PROTO_SOURCE_FILES}) get_filename_component(DIR ${input_proto} DIRECTORY) get_filename_component(FILE_NAME ${input_proto} NAME_WE)
set(OUTPUT_CXX_HEADER "${PROTO_CXX_OUT}/${FILE_NAME}.pb.h") set(OUTPUT_CXX_SOURCE "${PROTO_CXX_OUT}/${FILE_NAME}.pb.cc") list(APPEND OUTPUT_SOURCES_CXX ${OUTPUT_CXX_HEADER} ${OUTPUT_CXX_SOURCE}) endforeach()
add_custom_command( OUTPUT ${OUTPUT_SOURCES_CXX} COMMAND ${PROTOC_CXX} --cpp_out=${PROTO_CXX_OUT} --proto_path=${PROTO_PATH} ${PROTO_SOURCE_FILES} DEPENDS ${PROTO_SOURCE_FILES} WORKING_DIRECTORY ${PROTO_PATH} COMMENT "Generate Cpp Protobuf Source Files" )
add_custom_target( compile_cxx_protos DEPENDS ${OUTPUT_SOURCES_CXX} )
set(PROTO_GEN_CXX_INCLUDE_DIRS ${PROTO_CXX_OUT} PARENT_SCOPE)
add_library(proto_gen_cxx ${OUTPUT_SOURCES_CXX}) target_link_libraries(proto_gen_cxx protobuf) add_dependencies(proto_gen_cxx compile_cxx_protos)
|