序列化与反序列化:面试官问“ROS2的消息是怎么传输的”,我居然只说了“DDS”
上篇聊了socket编程。数据通过网络发送的时候传的是字节流。但你的程序里用的是结构体、类对象——怎么把内存中的对象变成字节流发出去接收方又怎么把字节流还原成对象这就是序列化与反序列化。在机器人开发里这个话题绕不开。ROS2的所有消息类型——sensor_msgs、geometry_msgs、nav_msgs——底层都是用序列化框架处理的。面试的时候如果你能讲清楚序列化的原理和几种方案的对比会加分不少。什么是序列化序列化就是把内存中的数据结构转换成可以存储或传输的格式通常是字节流。反序列化就是反过来——从字节流恢复出原来的数据结构。struct Pose { double x, y, z; double roll, pitch, yaw; }; // 序列化Pose → 字节流 Pose pose {1.0, 2.0, 0.0, 0.1, 0.2, 0.3}; // 怎么把pose变成可以send()的字节 // 反序列化字节流 → Pose // 收到一串字节怎么恢复成Pose对象最简单的做法是直接发送结构体的内存Pose pose {1.0, 2.0, 0.0, 0.1, 0.2, 0.3}; send(fd, pose, sizeof(Pose), 0); // 直接发送内存 Pose received; recv(fd, received, sizeof(Pose), 0); // 直接接收这种做法叫原始二进制序列化。快是快但有几个大问题第一不同平台的字节序可能不同。x86是小端序某些ARM芯片是大端序。同一个double在小端机器上和大端机器上的字节排列是反的。直接发内存过去对方解析出来就是乱码。第二内存对齐的问题。上篇讲过结构体成员之间有填充字节。不同编译器、不同平台的对齐方式可能不一样。同一份代码在两台机器上编译sizeof(Pose)可能不一样。第三结构体变了怎么办加一个字段、删一个字段发送方和接收方必须同步更新否则解析就错了。Protocol BuffersGoogle的Protocol Buffersprotobuf就是来解决这些问题的。它是ROS2消息系统的底层序列化方案。protobuf的使用分两步先写一个.proto文件定义数据结构然后用protoc编译器生成C代码。// pose.proto syntax proto3; message Pose { double x 1; double y 2; double z 3; double roll 4; double pitch 5; double yaw 6; }每个字段后面的数字是标签号不是默认值。protobuf用这个标签号来标识字段而不是字段名。这就是为什么protobuf可以向前向后兼容——加新字段不影响旧代码旧代码遇到不认识的标签号会跳过。生成的C代码这样用// 序列化 Pose pose; pose.set_x(1.0); pose.set_y(2.0); pose.set_z(0.0); pose.set_roll(0.1); std::string buffer; pose.SerializeToString(buffer); send(fd, buffer.c_str(), buffer.size(), 0); // 反序列化 Pose received; char buf[1024]; int n recv(fd, buf, sizeof(buf), 0); received.ParseFromArray(buf, n); cout received.x() endl;protobuf的优势跨平台、跨语言、向后兼容、编码紧凑。它把数据编码成变长的字节——小的数字用更少的字节大的数字用更多字节。比固定大小的二进制格式更省空间。其他序列化方案对比面试可能会问除了protobuf还有什么选择。常见的有这几种JSON文本格式人可读但体积大、解析慢。适合配置文件和Web API不适合高频的机器人数据传输。{x: 1.0, y: 2.0, z: 0.0, roll: 0.1}MessagePack二进制JSON体积小、速度快但不需要预定义schema。灵活性高但没有版本兼容性保证。FlatBuffersGoogle的另一个序列化库和protobuf类似但支持零拷贝反序列化——序列化后的数据可以直接访问字段不需要解析。适合对延迟极度敏感的场景。在机器人开发里protobuf是事实标准。ROS2的IDL接口定义语言就是基于IDL标准底层用CDRCommon Data Representation编码和protobuf的思路类似。在机器人开发中的实际应用如果你在做多机器人系统机器人之间传输自定义消息的时候序列化是绑不开的。一个典型的流程// 发送方序列化自定义消息 struct DetectionResult { int objectId; double confidence; double bbox[4]; // x, y, w, h }; // 用protobuf定义消息 // message DetectionResult { // int32 object_id 1; // double confidence 2; // repeated double bbox 3; // } DetectionResultMsg msg; msg.set_object_id(result.objectId); msg.set_confidence(result.confidence); for (int i 0; i 4; i) msg.add_bbox(result.bbox[i]); std::string buffer; msg.SerializeToString(buffer); // 通过网络发送buffer面试中的关键考点序列化和反序列化的区别序列化是对象→字节流反序列化是字节流→对象。protobuf为什么能跨平台它用变长编码varint来编码整数用IEEE 754标准来编码浮点数不依赖特定平台的内存布局。protobuf的向前向后兼容怎么实现的每个字段有唯一的标签号。反序列化时遇到不认识的标签号就跳过。所以新版本加的字段旧版本不会报错只是读不到这个字段的值。序列化性能对比实际测一下几种序列化方案的性能差异。对一个包含10个double字段的结构体做序列化/反序列化各10万次protobuf耗时约320毫秒序列化后大小82字节FlatBuffers序列化约15毫秒零拷贝反序列化大小80字节JSON用nlohmann/json库耗时约2800毫秒大小约150字节直接memcpy只用了8毫秒大小80字节。protobuf的速度大约是直接序列化的1/40但换来了跨平台和兼容性。JSON的体积和速度都不占优在高频通信场景下确实不合适。给正在准备面试的你序列化在机器人面试里属于了解即可的知识点。你不需要把protobuf的API全背下来但要理解序列化的概念、为什么需要序列化、protobuf解决了什么问题。如果你做过ROS2项目面试官问ROS2的消息是怎么传输的你可以从DDS讲到序列化——ROS2用DDS做通信中间件消息在发送前会被序列化成CDR格式的字节流接收方再反序列化回来。再补充一个面试加分点序列化方案的安全考虑。在机器人系统里如果你从不可信的来源接收序列化数据比如外部网络反序列化时可能触发安全漏洞。protobuf本身有消息大小限制和嵌套深度限制能防止一部分攻击。但如果你用JSON这种文本格式要特别注意字符串编码问题和超大数字导致的溢出。在安全敏感的场景下建议对反序列化的输入做大小限制和格式校验不要直接信任外部数据。面试时如果你能提到序列化的安全性考虑面试官会觉得你的工程思维很全面。下篇进入数学库板块——Eigen矩阵库入门。机器人数学运算的标配工具从线性代数到坐标变换Eigen无处不在。如果这篇文章对你有帮助欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。「机器人软件开发面试·从入门到精通」连载系列上一篇第59篇 C网络编程基础——socket编程与机器人通信 下一篇预告第61篇 Eigen矩阵库入门——机器人数学运算的标配工具有任何问题欢迎评论区留言我会尽量回复。