boost::serialize(序列化)和boost::deserialize(反序列化)类对象型数据并通过socket网络传输

xiaoxiao2021-02-28  64

***[欢迎访问我的个人博客: $zengzeyu.com$](zengzeyu.com)***

前言


![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xMDAyODA1OC02Njg2YjcxNzQyNjA4NjlhLnBuZw?x-oss-process=image/format,png) 用户界面显示后台处理结果

在自动驾驶领域,激光雷达与主机通过网线连接,实现二者的实时通信(主要是激光雷达发送扫描数据到主机)。同时,在接收到雷达数据之后,后台处理系统到前端用户显示界面,也需要通过上述方法进行通信,因为后台处理系统一般都不自带显示器,例如PX-2,TX2等平台就提供网线接口。 本文以速腾聚创公司16线激光雷达生成数据,处理后台系统处理后点云为例,进行分割分类数据传输到前端用户界面进行显示。 主要用到了以下两个方面:

boost::serialize 和 boost::deserialize 函数socket sendto() 和 recvfrom() 函数

本文首先介绍不使用boost序列化方法进行传输方法,之后再介绍boost序列化方法进行数据传输的方法。主要梳理boost::serialize(序列化)和boost::deserialize(反序列化)相关内容,socket部分稍作介绍。关于二者的相关知识链接,请参阅底部参考文献。

1. 非序列化数据传输方法


1.1 数据类格式

非序列化数据传输方法中数据类表示如下:

struct CommunicationMsg { Header header; PoseMsg pose; char perceptions[20000]; }

header 和 pose为另外的数据类,perceptions为char型数据容器。在非序列化方法下,将CommunicationMsg通过socket进行传输时,必须保证接收端的CommunicationMsg也必须为同样的数据顺序格式,即header、pose和perceptions三者顺序以及结构必须保持一致。

1.2 发送端代码

非序列化发送端代码如下:

bool sendMsg(const std::vector<PerceptOutput> &datas, const Header &header, const PoseMsg &pose) { CommunicationMsg send_msg; send_msg.header = header; send_msg.pose = pose; std::vector<PerceptResultMsg> msgs = con--- title: boost::serialize(序列化)和boost::deserialize(反序列化)类对象型数据并通过socket网络传输 date: 2018-05-02 22:55:54 categories: BOOST tags: - socket - boost - c++ ---vertToMsg(datas); /// origin send std::string tmp_string = toString(msgs); send_msg.header.length = static_cast<int>(tmp_string.size()); for(int i = 0; i < static_cast<int>(tmp_string.size()); ++i) { send_msg.perceptions[i] = tmp_string[i]; } char pot[20480]; memset(pot, 0, sizeof(pot)); memcpy(pot, &send_msg, sizeof(send_msg)); sendto(sender_sockfd_, pot, sizeof(pot), 0, (struct sockaddr *) &sender_dest_addr_, sizeof(sender_dest_addr_)); return true; }

可以看到上述代码中的pot变量为固定长度20480,20480这个数字是根据自己的数据长度来定,唯一的原则就是要保证所要发送的数据长度必须小于该数大小。这里就体现出非序列化方法中固定数组长度弊端:

数据小于该数组长度时,造成网络带宽的浪费;数据大于该数组长度时,造成数据溢出。

1.3 接收端代码

bool receMsg(std::vector<PerceptResultMsg> &msgs, Header &header, PoseMsg &pose) { char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16 socklen_t cliaddr_len = sizeof(receiver_dest_addr_); --- title: boost::serialize(序列化)和boost::deserialize(反序列化)类对象型数据并通过socket网络传输 date: 2018-05-02 22:55:54 categories: BOOST tags: - socket - boost - c++ --- char recv_buf[50000] = {0}; int recv_len = recvfrom(receiver_sockfd_, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &receiver_dest_addr_, &cliaddr_len); inet_ntop(AF_INET, &receiver_dest_addr_.sin_addr, cli_ip, INET_ADDRSTRLEN); /// origin receive CommunicationMsg *tmp_msg = new CommunicationMsg; tmp_msg = (CommunicationMsg *) recv_buf; int str_len = tmp_msg->header.length; std::string tmp_str; tmp_str.resize(str_len); for(int i = 0; i < str_len; ++i) { tmp_str[i] = tmp_msg->perceptions[i]; } msgs = toData(tmp_str); header = tmp_msg->header; pose = tmp_msg->pose; return true; }

接收端代码中recv_buf固定大小为50000,由于数据在完全接收之前不知道数据长度为多少,所以必须使用更大的数组容器来盛放接收数据。此处,能想到的可以讲数据长度变量和数据本身分开成两次先后接收,可以先接收到数据长度,然后使用该长度去接收数据,不过这样使得操作更加麻烦,可能用更加智能的方法,本文未涉及。 接收端将接收到的数据通过此行代码tmp_msg = (CommunicationMsg *) recv_buf;进行强制类型转换,该方法监督粗暴,但后患无穷,具体会导致哪些不容易发现的bug,本文未进行深究,但此方法在使用中也是可行方案之一。这个方法依据数据在数组中的放置方式,完全按照该类结构来储存,所以可以采用此方法。此方法属于C/C++语言的自带特性中的强制类型转换应用。

2. boost serialize (序列化) 和 boost deserialize (反序列化)


对于发送和接收消息,我觉得可以通过举一个例子来形象的理解:谍战中的密码报文发送与接收。当友军持有相同的密码解读字典时,发送密码一方通过将需要发送的信息,通过查询密码字典翻译为密码,然后通过发报机发送出去,接收一方首先记录下接收到的信息,然后在通过查询密码字典将信息翻译出来,从而得到真实的信息。只要理解了上面的原理,将该模型抽象出来,就会在生活中发现许多相似的场景:理解相同语言人之间的对话,收音机调到某一频率收听节目,与黄金等值的货币之间的兑换等等。这就是 boost 序列化的最形象的理解。

2.1 类对象数据序列化

数据格式的序列化可分为:XML格式,文本格式(text)和二进制格式(binary)。二进制格式虽然在传输过程中速度会更快,但是会破坏类对象型数据数据结构,所以不采用此方法。文本格式是最容易理解和输出可是化的格式,所见即所得,所以本文采用此格式进行数据传输。 使用文本格式序列化须包含以下头文件:#include <boost/archive/text_iarchive.hpp>和#include <boost/archive/text_oarchive.hpp>。另外,boost自带对STL库的数据支持,例如本文使用到的对std::vector支持,需要包含头文件#include <boost/serialization/vector.hpp>。 类对象数据是否侵入式序列化有两种方式:侵入式序列化(intrusive)和非侵入式序列化(non-intrusive)。

关于二者的介绍,[参考文献1]中进行了详细描述。本文将以第1章中数据为例,使用侵入式序列化进行简单的介绍。

侵入式序列化

侵入式序列化操作相关代码:

std::vector<PerceptResultMsg> pcep_msg; template<typename Archive> void serialize(Archive & ar, const unsigned int version) { ar & a; ar & b; ... }

以本文数据类型为例:

//boost #include <boost/archive/text_iarchive.hpp> #include <boost/archive/text_oarchive.hpp> #include <boost/serialization/vector.hpp> // header struct Header{ //public: // Header() {} // Header( unsigned short int& in_head, unsigned short int& in_object_num, unsigned int& in_length, // unsigned long int& in_frame_id, unsigned long int& in_timestamp): // head(in_head), object_num(in_object_num), length(in_length), frame_id(in_frame_id), timestamp(in_timestamp){} friend class boost::serialization::access; template<class Archive> void serialize(Archive& ar, const unsigned int version) { ar & head; ar & object_num; ar & length; ar & frame_id; ar & timestamp; } unsigned short int head; unsigned short int object_num; unsigned int length; unsigned long int frame_id; unsigned long int timestamp; }; struct CommunicationMsg{ friend class boost::serialization::access; Header header; PoseMsg pose; // char perceptions[20000]; // intrusive std::vector<PerceptResultMsg> pcep_msg; template<typename Archive> void serialize(Archive & ar, const unsigned int version) { ar & header; ar & pose; ar & pcep_msg; }

既然是“侵入式”,那么就要对原有数据类进行更改,上述代码中的更改将std::vector<PerceptResultMsg> pcep_msg替代原有char perceptions[20000]为容器。对于侵入式序列化的类数据每一个数据成员,同样要进行侵入式序列化处理,因为boost serialize只对其支持的数据结构(基本数据类型和STL库里的数据类型)进行序列化操作,对于用户自定义的类型数据,无法自动进行序列化处理。代码中以struct header子数据成员为例进行了侵入式序列化操作。

2.2 序列化操作代码

std::string Serialize(const CommunicationMsg &msg) { std::ostringstream archiveStream; boost::archive::text_oarchive archive(archiveStream); archive << msg; return archiveStream.str(); }

将CommunicationMsg类型对象传入该函数可得到string类型数据传输流,用以 socket 通信。

2.3 接收端反序列化

为了反序列化出原有CommunicationMsg类型数据对象,那么定义声明CommunicationMsg类型文件必须在接收端同样有一份,这样反序列化操作才能根据该文件得出经 socket 传输过来的数据属于哪个变量。

CommunicationMsg DeSerialize(const std::string &message) { CommunicationMsg msg; std::istringstream archiveStream(message); boost::archive::text_iarchive archive(archiveStream); archive >> msg; return msg; }

3. socket 通信


基于 socket 的通信,本文使用了以下两个函数:

sendto(sender_sockfd_, pot_1, sizeof(pot_1), 0, (struct sockaddr *) &sender_dest_addr_, sizeof(sender_dest_addr_)); recvfrom(receiver_sockfd_, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &receiver_dest_addr_, &cliaddr_len);

3.1 序列化数据发送端代码

bool sendMsg(const std::vector<PerceptOutput> &datas, const Header &header, const PoseMsg &pose) { CommunicationMsg send_msg; send_msg.header = header; send_msg.pose = pose; std::vector<PerceptResultMsg> msgs = convertToMsg(datas); /// serialize send try { send_msg.pcep_msg.resize( msgs.size() ); for (int i = 0; i < msgs.size(); ++i) { send_msg.pcep_msg[i] = msgs[i]; } std::string send_str = this->Serialize( send_msg ); char pot_1[send_str.length()]; for (int i = 0; i < send_str.length(); ++i) { pot_1[i] = send_str.c_str()[i]; } sendto(sender_sockfd_, pot_1, sizeof(pot_1), 0, (struct sockaddr *) &sender_dest_addr_, sizeof(sender_dest_addr_)); } catch ( std::exception& e ) { std::cerr << e.what() << std::endl; } return true; }

至于为什么使用异常处理模块,考虑到偶尔有无法序列化的对象,防止程序崩溃继续运行的手段,对于某一帧点云数据无法传输序列化,通过异常处理手段来跳过是可以忍受的。反序列化端接收代码同样如此。

3.2 序列化数据接收端代码

bool receMsg(std::vector<PerceptResultMsg> &msgs, Header &header, PoseMsg &pose) { char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16 socklen_t cliaddr_len = sizeof(receiver_dest_addr_); char recv_buf[50000] = {0}; int recv_len = recvfrom(receiver_sockfd_, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &receiver_dest_addr_, &cliaddr_len); inet_ntop(AF_INET, &receiver_dest_addr_.sin_addr, cli_ip, INET_ADDRSTRLEN); /// ---------- boost deserialize --------------- std::string rec_str; for (int i = 0; i < recv_len; ++i) { rec_str += recv_buf[i]; } try { CommunicationMsg boost_msg; boost_msg = this->Deserialize( rec_str ); std::cout << "boost_msg.pcep_msg.size(): " << boost_msg.pcep_msg.size() << std::endl; header = boost_msg.header; pose = boost_msg.pose; msgs = boost_msg.pcep_msg; } catch ( std::exception& e ) { std::cerr << e.what() << std::endl; } return true; }

后记


此次小的更新虽然只是一个很小部分代码更新,对于整体性能影响也不大,顶多对网络带宽占用率有一点降低,提升一点传输速度。

以上。


参考文献:

Boost - 序列化 (Serialization)boost::serialize 官方教程使用boost库序列化传输对象
转载请注明原文地址: https://www.6miu.com/read-2614587.html

最新回复(0)