lcm通信库介绍与使用指南

1. LCM 简介

LCM 全称是 Lightweight Communications and Marshalling ,它是一个为 机器人系统、多进程通信 等场景设计的 高效消息传输与数据打包框架

它最早由麻省理工学院(MIT)为 DARPA Urban Challenge 项目开发,用于在多个进程、多个计算机之间进行低延迟的消息传输和类型安全的数据序列化。

主要特点:

  • 轻量级:只提供核心的通信和序列化功能,没有太多依赖。
  • 跨语言、跨平台:支持 C、C++、Java、Python、Lua、MATLAB 等。
  • 类型安全 :消息格式通过专用的 .lcm 描述文件定义,并自动生成对应编程语言的代码。
  • 低延迟:采用 UDP 多播(也可以配置 TCP/Unicast)。
  • 支持多进程和多机通信

2. LCM 架构概述

LCM 由几部分组成:

  1. 消息类型定义

    • 用户通过 .lcm 文件定义消息类型(类似于IDL/ProtoBuf)
    • 编译后自动生成各语言的类/结构体,以及序列化/反序列化代码。
  2. 订阅-发布模型(pub/sub)

    • Publisher:发布者,把消息编码后发送到特定的信道(channel)。
    • Subscriber:订阅者,监听某个信道收到消息时触发回调处理函数。
    • 信道是按名字划分的(字符串),比如 "EXAMPLE"
  3. 传输层

    • 默认使用 UDP multicast (避免单独的Broker,分布式无中心结构)。
    • 也可以使用 TCP 单播(需要手动配置)。

3. 安装 LCM

Ubuntu/Debian

bash 复制代码
sudo apt-get update
sudo apt-get install liblcm-dev lcm

但是我的lcm其实是源码安装的,我是ubuntu20.04,还需要根据README.md回退到一个支持的commit,然后编译安装才行

macOS (brew)

bash 复制代码
brew install lcm

Python

bash 复制代码
pip install lcm

(需要先系统安装核心库)


4. 定义消息类型

LCM 的消息描述文件用 .lcm 扩展名,例如 example_t.lcm

lcm 复制代码
package exlcm;

struct example_t
{
    int64_t    timestamp;
    double     position[3];
    double     orientation[4];
    int32_t    num_ranges;
    int16_t    ranges[num_ranges];
    string     name;
    boolean    enabled;
}

解释:

  • package 指定包名(对应代码生成的命名空间/模块)
  • struct 定义消息结构
  • 支持基础类型 (int8_t, int16_t, int32_t, int64_t, float, double, string, boolean) 以及数组(定长/变长)
  • 变长数组需前面有一个字段表示长度

5. 生成代码

假设你想用 Python 和 C++:

bash 复制代码
lcm-gen -p example_t.lcm    # 生成 Python 代码
lcm-gen -x example_t.lcm    # 生成 C++ 代码

生成后会在对应目录下出现:

  • Python: exlcm/example_t.py
  • C++: exlcm/example_t.hpp

6. Python 使用示例

发布端 publish.py

python 复制代码
import lcm
from exlcm import example_t
import time

lc = lcm.LCM()
msg = example_t()

msg.timestamp = int(time.time() * 1e6)
msg.position = [1.0, 2.0, 3.5]
msg.orientation = [0, 0, 0, 1]
msg.num_ranges = 3
msg.ranges = [100, 200, 300]
msg.name = "Hello LCM"
msg.enabled = True

lc.publish("EXAMPLE", msg.encode())

订阅端 subscribe.py

python 复制代码
import lcm
from exlcm import example_t

def my_handler(channel, data):
    msg = example_t.decode(data)
    print("Received message on channel \"%s\"" % channel)
    print("   timestamp   = %s" % msg.timestamp)
    print("   position    = %s" % list(msg.position))
    print("   orientation = %s" % list(msg.orientation))
    print("   ranges: %s" % list(msg.ranges))
    print("   name        = %s" % msg.name)
    print("   enabled     = %s" % msg.enabled)

lc = lcm.LCM()
subscription = lc.subscribe("EXAMPLE", my_handler)

try:
    while True:
        lc.handle()
except KeyboardInterrupt:
    pass

运行:

  1. 启动订阅端

    bash 复制代码
    python subscribe.py
  2. 发布一条消息

    bash 复制代码
    python publish.py

7. C++ 使用示例

publisher.cpp

cpp 复制代码
#include <lcm/lcm-cpp.hpp>
#include "exlcm/example_t.hpp"
#include <iostream>
#include <ctime>

int main()
{
    lcm::LCM lcm;
    if(!lcm.good())
        return 1;

    exlcm::example_t msg;

    msg.timestamp = static_cast<int64_t>(time(NULL) * 1000000);
    msg.position[0] = 1.0;
    msg.position[1] = 2.0;
    msg.position[2] = 3.5;
    msg.orientation[0] = 0;
    msg.orientation[1] = 0;
    msg.orientation[2] = 0;
    msg.orientation[3] = 1;
    msg.num_ranges = 3;
    msg.ranges.resize(3);
    msg.ranges[0] = 100;
    msg.ranges[1] = 200;
    msg.ranges[2] = 300;
    msg.name = "Hello LCM";
    msg.enabled = true;

    lcm.publish("EXAMPLE", &msg);

    return 0;
}

subscriber.cpp

cpp 复制代码
#include <lcm/lcm-cpp.hpp>
#include "exlcm/example_t.hpp"
#include <iostream>

class Handler
{
public:
    void handleMessage(const lcm::ReceiveBuffer* rbuf,
                       const std::string& chan,
                       const exlcm::example_t* msg)
    {
        std::cout << "Received message on channel " << chan << std::endl;
        std::cout << "  timestamp   = " << msg->timestamp << std::endl;
    }
};

int main()
{
    lcm::LCM lcm;
    if(!lcm.good())
        return 1;

    Handler handler;
    lcm.subscribe("EXAMPLE", &Handler::handleMessage, &handler);

    while(0 == lcm.handle());

    return 0;
}

编译链接需要 -llcm


8. 调试工具

LCM 提供了很多可视化工具(基于 Java Swing):

  • lcm-spy :查看当前网络中传输的消息

    bash 复制代码
    lcm-spy
  • lcm-logplayer:回放记录文件

  • lcm-logger:记录通信到日志文件


9. 环境变量与配置

常用环境变量:

  • LCM_DEFAULT_URL:设定默认通信URL,格式:
    • UDP 多播:udpm://239.255.76.67:7667?ttl=1
    • TCP:tcpq://<host>:<port>
  • 更改多播组 IP 与端口可避免冲突。

10. 使用注意事项

  • UDP 多播可能受交换机路由限制,在不同网络段要确保多播可达。
  • 大消息最好分片或使用 TCP 传输模式。
  • Python 下,lc.handle() 会阻塞等待一个消息,可以用 lc.handle_timeout(ms) 实现超时。
  • 订阅和发布的数据类型必须匹配,否则解码会失败。

如果你愿意的话,我可以帮你再画一张 LCM 工作流程图 ,帮助你更直观地理解它的运行过程。

要我画吗?