序列化就是把内存中的数据结构(比如一个包含x、y、z的结构体)转换成连续的字节流,方便在网络中传输。反序列化是逆过程——接收端拿到字节流后还原成原来的数据结构。
生活类比:序列化就像把一堆散乱的乐高零件按说明书打包成一个快递包裹,反序列化就是收货后拆箱、按图纸重新拼装。
| 阶段 | 数据形态 | 存储位置 | 能否跨网络传输 |
|---|---|---|---|
| 序列化前 | C语言结构体 / 类对象 | 内存(栈/堆) | 否 |
| 序列化后 | 连续的字节数组 | 内存缓冲区 | 是 |
| 反序列化后 | C语言结构体 / 类对象 | 内存(栈/堆) | 否 |
ROS2的消息用 .msg 文件定义,编译器将其转换为目标语言的代码。每个 .msg 文件会被映射为DDS底层的IDL(Interface Definition Language)描述,再由DDS厂商的代码生成器产生序列化/反序列化代码。
下面是一个真实的消息定义——geometry_msgs/msg/Point.msg:
# 三维空间中的一个点
float64 x
float64 y
float64 z
编译器会根据这个定义生成对应的C++或Python类,并自动附带序列化与反序列化方法。你只需要像操作普通对象一样赋值和发布,底层序列化过程由框架自动完成。
OMG制定的跨平台数据类型描述标准。ROS2的.msg文件最终会被转换为IDL格式,再由DDS厂商(如eProsima Fast DDS)的代码生成器处理。
OMG标准中定义的序列化编码格式。ROS2默认使用CDR作为消息的二进制编码方案,支持小端/大端两种字节序。
CDR对每种基本数据类型都有固定的编码长度和排列规则。复杂类型(结构体、数组、字符串)由基本类型组合而成,编码时按顺序依次排列。
| ROS2数据类型 | CDR编码字节数 | 说明 |
|---|---|---|
int8 / uint8 | 1字节 | 直接编码 |
int16 / uint16 | 2字节 | 对齐到2字节边界 |
int32 / uint32 / float32 | 4字节 | 对齐到4字节边界 |
int64 / uint64 / float64 | 8字节 | 对齐到8字节边界 |
string | 4字节长度前缀 + 内容 | 长度前缀为uint32类型 |
静态数组 T[N] | N × T字节数 | 连续排列,无额外开销 |
举个例子,一个 Point 消息包含三个 float64 字段,序列化后的字节流长度为 8×3 = 24 字节(不计算任何额外头部)。如果消息中包含 string 类型,则总长度 = 4 + 字符串实际字符数 + 其他字段长度。
uint8 后直接跟 float64,会插入7个填充字节。消息字段排列顺序影响最终大小。string 序列化后带4字节长度前缀,接收端必须正确解析长度再读取内容,不能硬编码偏移量。string[] 数组的自定义消息移植到资源受限平台时,发现序列化后的缓冲区大小经常超出预期。原因是每个字符串都有4字节长度前缀,且数组元素之间可能存在对齐填充。建议在定义消息前先用 ros2 interface show 查看实际字节布局,或者编写一个小的测试节点打印序列化长度,避免在运行时才发现内存不足。
序列化后的消息大小直接影响网络带宽占用和延迟。对于高频话题(如里程计、激光扫描),减少不必要的字段能显著降低负载。
在开发实际项目时,我曾在Cortex-M7平台上遇到过序列化耗时导致话题周期抖动的问题。排查后发现是消息中一个 float64[64] 数组字段导致CDR序列化循环耗时过长。最后的解决方案是改用定长 float32[] 并缩小数组规模,将单次序列化时间从2.3ms降到了0.7ms。
.msg 文件,包含至少三种不同类型字段(如 int32、float64、string),用 ros2 msg show 命令查看其结构。msg.serialized_data_size() 打印消息序列化后的字节数。string 字段换成定长字符数组,对比序列化后大小的变化。.msg 文件中将字段排列为 uint8 后跟 float64,比反过来排列更节省空间。( )