LCM(Lightweight Communications and Marshalling)是一个专为实时系统设计的轻量级通信与数据序列化开源库,广泛应用于机器人、自动驾驶、嵌入式系统等需要高效、低延迟数据传输的场景。其核心特点是轻量、高效、跨平台、跨语言,专注于解决实时系统中多模块间的快速数据交换问题。

一、LCM的核心特性

  1. 高效的通信机制
    基于UDP协议实现发布-订阅(Publish-Subscribe)通信,支持多对多异步通信,传输延迟低(微秒级),适合高频实时数据(如传感器数据流)。

  2. 跨语言支持
    原生支持C、C++、Python、Java、C#等多种语言,不同语言编写的模块可通过LCM直接通信(依赖统一的消息定义)。

  3. 紧凑的数据序列化
    通过自定义消息格式(.lcm文件)定义数据结构,编译后生成各语言的序列化/反序列化代码,采用二进制编码,数据体积小,解析速度快(比JSON、XML等文本格式高效得多)。

  4. 轻量级设计
    无中心节点(去中心化),无需复杂的服务发现机制,库体积小(核心库仅几百KB),资源占用低,适合嵌入式环境。

  5. 灵活的通信模式
    支持发布-订阅(多模块共享数据)和点对点(定向传输)两种模式,可通过网络(跨设备)或本地进程间通信(同一设备内)。

二、LCM的工作原理

LCM的通信流程基于“消息定义-代码生成-发布/订阅”三步:

  1. 消息定义(.lcm文件)
    用户通过.lcm文件定义数据结构(类似ROS的.msg文件),例如定义一个传感器数据消息:

    # 文件名:sensor_data.lcm
    package example;  # 消息所属包(类似命名空间)
    
    struct SensorData {
        int64_t timestamp;  // 时间戳(微秒)
        float x;            // x轴数据
        float y;            // y轴数据
        float z;            // z轴数据
        string frame_id;    // 坐标系
    }
    
  2. 代码生成
    使用LCM提供的lcm-gen工具,根据.lcm文件生成目标语言的绑定代码(包含消息的序列化/反序列化逻辑)。例如:

    • 生成C++代码:lcm-gen -x sensor_data.lcm(生成.hpp.cpp文件)
    • 生成Python代码:lcm-gen -p sensor_data.lcm(生成.py文件)
  3. 发布与订阅

    • 发布者(Publisher):填充消息数据,通过LCM实例将消息发布到指定“通道”(Channel,类似ROS的“话题”)。
    • 订阅者(Subscriber):订阅指定通道,当有消息到达时,LCM自动调用回调函数处理数据。

三、LCM的使用步骤(以C++和Python为例)

步骤1:安装LCM
  • Ubuntu
    sudo apt-get install liblcm-dev lcm
    
  • 源码编译(适用于其他系统):
    LCM官网下载源码,编译安装:
    mkdir build && cd build
    cmake ..
    make && sudo make install
    
步骤2:定义消息(.lcm文件)

创建sensor_data.lcm(如上述示例),定义需要传输的数据结构。

步骤3:生成语言绑定代码
# 生成C++代码(生成 example/sensor_data.hpp 和 example/sensor_data.cpp)
lcm-gen -x sensor_data.lcm

# 生成Python代码(生成 example/sensor_data.py)
lcm-gen -p sensor_data.lcm
步骤4:编写发布者与订阅者代码
示例1:C++ 发布者(publisher.cpp)
#include <lcm/lcm-cpp.hpp>
#include "example/sensor_data.hpp"  // 生成的消息头文件
#include <iostream>

int main() {
    // 创建LCM实例(自动处理网络配置)
    lcm::LCM lcm;
    if (!lcm.good()) {
        std::cerr << "LCM初始化失败!" << std::endl;
        return 1;
    }

    // 填充消息数据
    example::SensorData msg;
    msg.timestamp = 123456789;  // 示例时间戳
    msg.x = 1.23f;
    msg.y = 4.56f;
    msg.z = 7.89f;
    msg.frame_id = "base_link";

    // 发布消息到通道 "SENSOR_DATA"
    lcm.publish("SENSOR_DATA", &msg);
    std::cout << "发布消息:timestamp=" << msg.timestamp << std::endl;

    return 0;
}
示例2:C++ 订阅者(subscriber.cpp)
#include <lcm/lcm-cpp.hpp>
#include "example/sensor_data.hpp"
#include <iostream>

// 消息回调函数:收到消息时自动调用
class SensorDataHandler {
public:
    void handleMessage(const lcm::ReceiveBuffer* rbuf,
                       const std::string& channel,
                       const example::SensorData* msg) {
        std::cout << "收到通道 " << channel << " 的消息:" << std::endl;
        std::cout << "timestamp: " << msg->timestamp << std::endl;
        std::cout << "x: " << msg->x << ", y: " << msg->y << ", z: " << msg->z << std::endl;
        std::cout << "frame_id: " << msg->frame_id << std::endl;
    }
};

int main() {
    lcm::LCM lcm;
    if (!lcm.good()) {
        std::cerr << "LCM初始化失败!" << std::endl;
        return 1;
    }

    SensorDataHandler handler;
    // 订阅通道 "SENSOR_DATA",绑定回调函数
    lcm.subscribe("SENSOR_DATA", &SensorDataHandler::handleMessage, &handler);

    std::cout << "等待消息...(按Ctrl+C退出)" << std::endl;
    while (0 == lcm.handle()) {  // 阻塞等待消息
        // 循环处理消息
    }

    return 0;
}
示例3:Python 发布者(publisher.py)
import lcm
from example import sensor_data  # 生成的Python消息类

# 创建LCM实例
lc = lcm.LCM()

# 填充消息
msg = sensor_data()
msg.timestamp = 123456789
msg.x = 1.23
msg.y = 4.56
msg.z = 7.89
msg.frame_id = "base_link"

# 发布到通道 "SENSOR_DATA"
lc.publish("SENSOR_DATA", msg.encode())
print(f"发布消息:timestamp={msg.timestamp}")
示例4:Python 订阅者(subscriber.py)
import lcm
from example import sensor_data

# 回调函数:处理收到的消息
def handle_message(channel, data):
    msg = sensor_data.decode(data)  # 反序列化消息
    print(f"收到通道 {channel} 的消息:")
    print(f"timestamp: {msg.timestamp}")
    print(f"x: {msg.x}, y: {msg.y}, z: {msg.z}")
    print(f"frame_id: {msg.frame_id}")

# 创建LCM实例并订阅通道
lc = lcm.LCM()
subscription = lc.subscribe("SENSOR_DATA", handle_message)

print("等待消息...(按Ctrl+C退出)")
try:
    while True:
        lc.handle()  # 阻塞等待消息
except KeyboardInterrupt:
    pass

lc.unsubscribe(subscription)
步骤5:编译与运行
  • C++编译(需链接LCM库):

    # 编译发布者
    g++ publisher.cpp example/sensor_data.cpp -o publisher -llcm
    
    # 编译订阅者
    g++ subscriber.cpp example/sensor_data.cpp -o subscriber -llcm
    
  • 运行

    1. 先启动订阅者:./subscriber
    2. 再启动发布者:./publisher(Python版本直接运行:python3 publisher.py

    订阅者会收到发布者发送的消息并打印。

四、LCM与ROS的对比

特性LCMROS 1/2
设计目标轻量、高效实时通信完整的机器人系统框架(通信+工具链)
通信机制基于UDP的发布-订阅/点对点基于TCP/UDP的话题/服务/动作
数据序列化二进制(高效紧凑)二进制(ROS 1)/CDR(ROS 2)
工具链简洁(仅通信核心)丰富(rviz、rosbag、参数服务器等)
跨语言支持原生支持多语言支持多语言(需适配)
资源占用极低(适合嵌入式)较高(功能丰富带来的开销)
适用场景高频实时数据传输、嵌入式系统复杂机器人系统(需要完整工具链)

五、总结

LCM是实时系统中高效数据通信的优选方案,尤其适合对延迟、资源占用敏感的场景(如自动驾驶传感器数据传输、机器人实时控制)。其核心优势在于轻量和高效,但缺乏ROS那样的完整工具链(如可视化、日志记录),因此常被用作ROS的补充,或在不需要复杂功能的场景中独立使用。

如果你的需求是“快速、低延迟地传输数据”,且不需要额外的机器人工具链,LCM会是比ROS更轻量的选择。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐