第8章 消息序列化技术

码枢沄社 · 嵌入式体系化教程平台
入门 👤 刚接触ROS2消息机制的初学者 🔧 理解话题/服务通信时数据如何编码传输
本章你将学到
  • 序列化与反序列化的核心概念
  • ROS2消息定义与IDL的关系
  • CDR序列化的工作机制
  • 消息编码对通信性能的影响
核心概念序列化反序列化CDRIDL

序列化与反序列化

消息对象
序列化
字节流
⬇ DDS网络传输 ⬇
消息对象
反序列化
字节流

序列化就是把内存中的数据结构(比如一个包含x、y、z的结构体)转换成连续的字节流,方便在网络中传输。反序列化是逆过程——接收端拿到字节流后还原成原来的数据结构。

生活类比:序列化就像把一堆散乱的乐高零件按说明书打包成一个快递包裹,反序列化就是收货后拆箱、按图纸重新拼装。

阶段数据形态存储位置能否跨网络传输
序列化前C语言结构体 / 类对象内存(栈/堆)
序列化后连续的字节数组内存缓冲区
反序列化后C语言结构体 / 类对象内存(栈/堆)
为什么必须序列化? 不同硬件平台(x86、ARM、RISC-V)的内存布局、字节序、对齐方式都不同。序列化提供了一套与平台无关的数据编码规范,确保消息在不同架构的节点间正确解析。

ROS2消息定义与IDL

ROS2的消息用 .msg 文件定义,编译器将其转换为目标语言的代码。每个 .msg 文件会被映射为DDS底层的IDL(Interface Definition Language)描述,再由DDS厂商的代码生成器产生序列化/反序列化代码。

下面是一个真实的消息定义——geometry_msgs/msg/Point.msg

# 三维空间中的一个点
float64 x
float64 y
float64 z

编译器会根据这个定义生成对应的C++或Python类,并自动附带序列化与反序列化方法。你只需要像操作普通对象一样赋值和发布,底层序列化过程由框架自动完成。

IDL(接口定义语言)

OMG制定的跨平台数据类型描述标准。ROS2的.msg文件最终会被转换为IDL格式,再由DDS厂商(如eProsima Fast DDS)的代码生成器处理。

CDR(公共数据表示)

OMG标准中定义的序列化编码格式。ROS2默认使用CDR作为消息的二进制编码方案,支持小端/大端两种字节序。

CDR序列化工作机制

CDR对每种基本数据类型都有固定的编码长度和排列规则。复杂类型(结构体、数组、字符串)由基本类型组合而成,编码时按顺序依次排列。

ROS2数据类型CDR编码字节数说明
int8 / uint81字节直接编码
int16 / uint162字节对齐到2字节边界
int32 / uint32 / float324字节对齐到4字节边界
int64 / uint64 / float648字节对齐到8字节边界
string4字节长度前缀 + 内容长度前缀为uint32类型
静态数组 T[N]N × T字节数连续排列,无额外开销

举个例子,一个 Point 消息包含三个 float64 字段,序列化后的字节流长度为 8×3 = 24 字节(不计算任何额外头部)。如果消息中包含 string 类型,则总长度 = 4 + 字符串实际字符数 + 其他字段长度。

常见误区
  1. 忽略字节对齐:CDR要求每个字段按自身长度对齐到相应边界。如果在 uint8 后直接跟 float64,会插入7个填充字节。消息字段排列顺序影响最终大小。
  2. string类型当作定长字段string 序列化后带4字节长度前缀,接收端必须正确解析长度再读取内容,不能硬编码偏移量。
  3. 混淆字节序:发送端和接收端的CDR字节序必须一致。ROS2默认使用小端(LE),如果手动构造字节流时用了大端,会导致解析错乱。
编者提示:我在将一个包含嵌套 string[] 数组的自定义消息移植到资源受限平台时,发现序列化后的缓冲区大小经常超出预期。原因是每个字符串都有4字节长度前缀,且数组元素之间可能存在对齐填充。建议在定义消息前先用 ros2 interface show 查看实际字节布局,或者编写一个小的测试节点打印序列化长度,避免在运行时才发现内存不足。

消息大小与性能

序列化后的消息大小直接影响网络带宽占用和延迟。对于高频话题(如里程计、激光扫描),减少不必要的字段能显著降低负载。

低频状态话题

  • 频率:1~10 Hz
  • 消息大小:几百字节到几KB
  • 序列化开销可忽略
  • 建议:可包含完整状态信息

高频控制话题

  • 频率:100~1000 Hz
  • 消息大小:几十字节
  • 序列化开销成为瓶颈
  • 建议:仅包含必要字段

大带宽数据话题

  • 频率:10~100 Hz
  • 消息大小:几百KB到几MB
  • 序列化耗时占比高
  • 建议:考虑分片或压缩

在开发实际项目时,我曾在Cortex-M7平台上遇到过序列化耗时导致话题周期抖动的问题。排查后发现是消息中一个 float64[64] 数组字段导致CDR序列化循环耗时过长。最后的解决方案是改用定长 float32[] 并缩小数组规模,将单次序列化时间从2.3ms降到了0.7ms。

动手试一试

  1. 创建一个自定义 .msg 文件,包含至少三种不同类型字段(如 int32float64string),用 ros2 msg show 命令查看其结构。
  2. 编写一个发布节点和一个订阅节点,在回调中用 msg.serialized_data_size() 打印消息序列化后的字节数。
  3. 将消息中的 string 字段换成定长字符数组,对比序列化后大小的变化。

检验你的理解

  1. 判断题:序列化后的字节流可以直接在不同字节序的平台上正确反序列化。( )
  2. 选择题:ROS2默认使用的序列化格式是? A. JSON B. XML C. CDR D. Protocol Buffers
  3. 判断题:在 .msg 文件中将字段排列为 uint8 后跟 float64,比反过来排列更节省空间。( )

本章小结

  • 序列化将内存数据结构转为字节流,反序列化是逆过程,两者是实现跨平台通信的基础。
  • ROS2使用OMG标准中的CDR作为序列化格式,与DDS深度绑定。
  • .msg 消息定义最终映射为IDL描述,由DDS代码生成器产生序列化代码。
  • CDR对基本类型有固定的编码长度和对齐规则,复杂类型由基本类型组合而成。
  • 消息字段的排列顺序和类型选择会影响序列化后的大小和性能,高频话题应精简字段。
码枢沄社 · 嵌入式体系化教程平台

3000+ 实战教程 · STM32 / Linux / RTOS / 汽车电子 / 物联网…
查看全部课程
← 上一章 返回目录 下一章 →