1. LCM 简介
LCM 全称是 Lightweight Communications and Marshalling ,它是一个为 机器人系统、多进程通信 等场景设计的 高效消息传输与数据打包框架 。
它最早由麻省理工学院(MIT)为 DARPA Urban Challenge 项目开发,用于在多个进程、多个计算机之间进行低延迟的消息传输和类型安全的数据序列化。
主要特点:
- 轻量级:只提供核心的通信和序列化功能,没有太多依赖。
- 跨语言、跨平台:支持 C、C++、Java、Python、Lua、MATLAB 等。
- 类型安全 :消息格式通过专用的
.lcm
描述文件定义,并自动生成对应编程语言的代码。 - 低延迟:采用 UDP 多播(也可以配置 TCP/Unicast)。
- 支持多进程和多机通信。
2. LCM 架构概述
LCM 由几部分组成:
-
消息类型定义
- 用户通过
.lcm
文件定义消息类型(类似于IDL/ProtoBuf) - 编译后自动生成各语言的类/结构体,以及序列化/反序列化代码。
- 用户通过
-
订阅-发布模型(pub/sub)
- Publisher:发布者,把消息编码后发送到特定的信道(channel)。
- Subscriber:订阅者,监听某个信道收到消息时触发回调处理函数。
- 信道是按名字划分的(字符串),比如
"EXAMPLE"
。
-
传输层
- 默认使用 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
运行:
-
启动订阅端
bashpython subscribe.py
-
发布一条消息
bashpython 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 :查看当前网络中传输的消息
bashlcm-spy
-
lcm-logplayer:回放记录文件
-
lcm-logger:记录通信到日志文件
9. 环境变量与配置
常用环境变量:
LCM_DEFAULT_URL
:设定默认通信URL,格式:- UDP 多播:
udpm://239.255.76.67:7667?ttl=1
- TCP:
tcpq://<host>:<port>
- UDP 多播:
- 更改多播组 IP 与端口可避免冲突。
10. 使用注意事项
- UDP 多播可能受交换机路由限制,在不同网络段要确保多播可达。
- 大消息最好分片或使用 TCP 传输模式。
- Python 下,
lc.handle()
会阻塞等待一个消息,可以用lc.handle_timeout(ms)
实现超时。 - 订阅和发布的数据类型必须匹配,否则解码会失败。
如果你愿意的话,我可以帮你再画一张 LCM 工作流程图 ,帮助你更直观地理解它的运行过程。
要我画吗?