LCM中间件入门(2):LCM核心实现原理解析

文章目录

        • 一、`good()`函数:LCM实例状态检查的实现原理
          • 1. 实现逻辑
          • 2. 简化代码示例(C语言核心逻辑)
        • 二、`publish()`:向指定channel发送消息的原理
          • 1. 完整流程拆解
          • 2. 简化代码示例(C++核心逻辑)
        • 三、`subscribe()`:接收指定channel消息的原理
          • 1. 完整流程拆解
          • 2. 简化代码示例(C++核心逻辑)
      • 四、整体协同机制总结

LCM的核心功能基于C语言实现(C++接口为其封装),其底层通过 UDP网络通信消息序列化回调管理实现发布-订阅模式。以下从代码原理层面解析关键函数的工作机制。

一、good()函数:LCM实例状态检查的实现原理

good()函数用于判断LCM实例是否初始化成功,其核心是检查LCM内部关键资源的有效性。

1. 实现逻辑

LCM实例(lcm_t结构体)的核心成员包括:

  • 网络套接字(socket_fd):用于收发数据的UDP套接字;
  • 线程状态(thread_running):接收消息的后台线程是否启动;
  • 错误码(error):记录初始化或运行中的错误状态。

good()函数的本质是检查这些成员是否处于“可用状态”:

  • 套接字是否成功创建(socket_fd != -1);
  • 后台线程是否正常运行(针对需要异步接收的模式);
  • 无致命错误(error == 0)。
2. 简化代码示例(C语言核心逻辑)
// LCM实例结构体(简化)
typedef struct {int socket_fd;          // UDP套接字描述符int thread_running;     // 接收线程状态(1=运行,0=停止)int error;              // 错误码(0=无错误)// 其他成员:回调表、组播地址等
} lcm_t;// good()函数实现
int lcm_good(lcm_t *lcm) {return (lcm != NULL && lcm->socket_fd != -1 && lcm->thread_running && lcm->error == 0);
}

在C++接口中,lcm::LCM::good()是对上述C函数的封装,返回bool类型。

二、publish():向指定channel发送消息的原理

publish()的核心是将消息序列化为字节流,并通过UDP组播发送到与channel关联的网络地址,同时在数据包中嵌入channel标识。

1. 完整流程拆解
  1. 消息序列化
    根据.lcm文件生成的编解码函数(如example_temperature_t_pack()),将消息结构体转换为二进制字节流(解决跨平台数据格式差异)。

  2. channel与网络地址映射
    LCM默认将channel名称映射为UDP组播地址(239.255.x.y,其中x.y由channel名称的哈希值计算得出),确保同一channel的消息仅被订阅该channel的节点接收。

  3. 数据包封装
    构造LCM协议数据包,格式为:

    [4字节魔数] + [4字节消息长度] + [channel名称] + [序列化的消息数据]
    

    魔数(0x4C434D00,即"LCM\0")用于接收方识别LCM数据包。

  4. UDP发送
    通过LCM实例的套接字将数据包发送到channel对应的组播地址。

2. 简化代码示例(C++核心逻辑)
// C++ publish()接口
void LCM::publish(const std::string& channel, const void* data, size_t len) {if (!good()) return;  // 检查实例状态// 1. 计算channel对应的组播地址(基于哈希)struct sockaddr_in addr;lcm_channel_to_multicast(channel.c_str(), &addr);  // 内部哈希映射// 2. 封装LCM协议头uint8_t header[8];header[0] = 0x4C; header[1] = 0x43; header[2] = 0x4D; header[3] = 0x00;  // 魔数*(uint32_t*)(header + 4) = htonl(len);  // 消息长度(网络字节序)// 3. 拼接完整数据包:头 + channel + 消息数据std::vector<uint8_t> packet;packet.insert(packet.end(), header, header + 8);packet.insert(packet.end(), channel.begin(), channel.end());packet.push_back('\0');  // channel以空字符结尾packet.insert(packet.end(), (uint8_t*)data, (uint8_t*)data + len);// 4. 通过UDP发送到组播地址sendto(lcm->socket_fd, packet.data(), packet.size(), 0,(struct sockaddr*)&addr, sizeof(addr));
}
三、subscribe():接收指定channel消息的原理

subscribe()的核心是注册回调函数并与channel绑定,后台线程接收数据包后,根据channel查找对应的回调并触发执行。

1. 完整流程拆解
  1. 回调函数注册
    订阅时,LCM将channel名称消息类型回调函数存储在内部的回调表(哈希表,channel -> 回调列表)中。

  2. 后台接收线程
    LCM初始化时启动一个后台线程,循环从套接字读取UDP数据包:

    • 解析数据包,验证魔数和格式;
    • 提取channel名称和序列化的消息数据。
  3. 消息路由与反序列化
    根据解析出的channel名称,在回调表中查找对应的回调函数:

    • 若找到,调用自动生成的反序列化函数(如example_temperature_t_unpack()),将字节流转换为消息结构体;
    • 调用注册的回调函数,传入消息结构体。
  4. 线程安全处理
    回调函数的执行在后台线程中进行,若需在多线程环境中使用,需用户自行添加同步机制(如互斥锁)。

2. 简化代码示例(C++核心逻辑)
// 回调表结构(简化):channel -> 回调函数列表
typedef struct {std::unordered_map<std::string, std::vector<Callback>> callbacks;std::mutex mutex;  // 保护回调表的线程安全
} CallbackTable;// 订阅函数实现
void LCM::subscribe(const std::string& channel, void (*callback)(const ReceiveBuffer*, const std::string&, void*),void* userdata) {std::lock_guard<std::mutex> lock(callback_table.mutex);// 将回调函数注册到channel对应的列表中callback_table.callbacks[channel].emplace_back(callback, userdata);
}// 后台接收线程逻辑
void receive_thread(lcm_t* lcm) {while (lcm->thread_running) {uint8_t buffer[65536];  // UDP最大包长struct sockaddr_in sender;socklen_t sender_len = sizeof(sender);ssize_t n = recvfrom(lcm->socket_fd, buffer, sizeof(buffer), 0,(struct sockaddr*)&sender, &sender_len);if (n <= 0) continue;// 解析数据包:检查魔数、提取channel和消息数据if (buffer[0] != 0x4C || buffer[1] != 0x43 || buffer[2] != 0x4D || buffer[3] != 0x00)continue;  // 非LCM数据包,忽略uint32_t msg_len = ntohl(*(uint32_t*)(buffer + 4));std::string channel = (char*)(buffer + 8);  // channel以空字符结尾const uint8_t* msg_data = buffer + 8 + channel.size() + 1;// 查找回调并执行std::lock_guard<std::mutex> lock(callback_table.mutex);auto it = callback_table.callbacks.find(channel);if (it != callback_table.callbacks.end()) {for (auto& cb : it->second) {// 构造接收缓冲区,调用回调ReceiveBuffer rbuf{msg_data, msg_len};cb.function(&rbuf, channel, cb.userdata);}}}
}

四、整体协同机制总结

工作原理交互图如下:

PublisherLCM_Pub (发布者实例)Network (UDP组播)LCM_Sub (订阅者实例)Subscriber初始化LCM实例创建lcm_t结构体1. 创建UDP套接字2. 启动接收线程3. 初始化错误码为0调用good()返回状态 (socket有效+线程运行+无错误)初始化LCM实例创建lcm_t结构体1. 创建UDP套接字2. 启动接收线程3. 初始化回调表(空)调用good()返回状态 (socket有效+线程运行+无错误)订阅通道THERMOMETER调用subscribe("THERMOMETER", 回调函数)1. 加锁保护回调表2. 将回调函数注册到"THERMOMETER"条目下订阅完成发布消息到通道构造Temperature消息(温度值、时间戳等)调用publish("THERMOMETER", 消息)publish()内部处理1. 消息序列化(调用自动生成的pack函数)2. 计算channel哈希映射到组播地址(239.255.x.y)3. 封装LCM数据包(魔数+长度+channel+序列化数据)通过UDP发送数据包到组播地址接收UDP数据包(含"THERMOMETER"标识)接收线程处理1. 验证魔数和数据包格式2. 提取channel("THERMOMETER")和序列化数据3. 消息反序列化(调用自动生成的unpack函数)4. 查找回调表中"THERMOMETER"对应的回调函数触发回调函数(传入反序列化后的消息)执行回调逻辑(打印温度数据等)PublisherLCM_Pub (发布者实例)Network (UDP组播)LCM_Sub (订阅者实例)Subscriber
  1. 初始化阶段lcm_t实例创建套接字、启动接收线程,`good(
  2. )`验证这些资源是否就绪。
  3. 发布阶段publish()将消息序列化,通过channel映射的组播地址发送UDP包,嵌入channel标识。
  4. 订阅阶段subscribe()将回调注册到channel对应的哈希表;接收线程解析UDP包,根据channel查找回调,反序列化消息后触发执行。

这种设计实现了无中心节点的轻量化通信,通过UDP组播和哈希映射保证低延迟,适用于实时系统中基于channel的高效数据交互。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/91893.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/91893.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nginx安装及配置

一.nginx安装1.1nginx概述1.1.1 nginx介绍Nginx是一款高性能的开源HTTP和反向代理服务器&#xff0c;是免费的、开源的、高性能的HTTP和反向代理服务器、邮件代理服务器、以及TCP/UDP代理服务器解决C10K问题&#xff08;10K Connections&#xff09;。同时也支持IMAP/POP3代理服…

SelectDB数据库,新一代实时数据仓库的全面解析与应用

摘要&#xff1a;SelectDB是一款基于Apache Doris的新一代实时数据仓库解决方案&#xff0c;具备实时极速、融合统一、弹性架构和开放生态四大核心特性。它采用云原生存算分离架构&#xff0c;支持秒级数据更新、毫秒级查询响应&#xff0c;在TPC-H等基准测试中性能超越传统系统…

自动驾驶的未来:多模态传感器钻机

伦敦大学学院博士生袁方正在建造多模态传感器钻机&#xff0c;以探索自动驾驶的未来。他的最新设置汇集了一套尖端传感器&#xff1a; &#x1f4e1; 60 GHz 雷达&#xff08;用于 Raspberry Pi 的 DreamHAT&#xff09;DreamRF &#x1f4f7; RGB 深度摄像头 &#xff08;Real…

13.Redis 的级联复制

Redis 的级联复制 即实现基于Slave节点的Slave 1. 修改 Slave 节点配置文件 # 第一个slave节点 [rootubuntu2204 ~]#vim /apps/redis/etc/redis.conf(大约在533行附近) replicaof 10.0.0.100 6379 masterauth 123456# 第二个slave节点 [rootubuntu2204 ~]#vim /apps/redis/etc/…

spring-ai-alibaba 学习(二十)——graph之检查点

前面学习了graph的基本概念&#xff0c;参数设置&#xff0c;特殊节点和边&#xff0c;今天学习一下检查点检查点可能名称比较抽象&#xff0c;换个名字可能比较容易理解&#xff0c;进度保存点或者存档点&#xff0c;可以类比游戏中保存当前游戏进度的存档进度主要用于人工介入…

sqli-labs:Less-19关卡详细解析

1. 思路&#x1f680; 本关的SQL语句为&#xff1a; $insert"INSERT INTO security.referers (referer, ip_address) VALUES ($uagent, $IP)";注入类型&#xff1a;字符串型&#xff08;单引号包裹&#xff09;、INSERT操作提示&#xff1a;参数需以闭合关键参数&a…

Java小红书源码1:1还原uniapp_仿小红书源码

在内容驱动型社交平台兴起的背景下&#xff0c;小红书作为图文/视频种草社区的代表&#xff0c;其产品结构与功能体验逐渐成为众多开发者与创业团队的模仿蓝本。本项目基于Java后端uni-app前端栈&#xff0c;完整复刻小红书主要功能&#xff0c;支持多端&#xff08;小程序、H5…

USB Type-C PD协议一文通

原文&#xff1a;https://www.richtek.com/Design%20Support/Technical%20Document/AN056?sc_langzh-TW译者&#xff1a;TrustZone1、概述 USB Type-C标准的出现是为了满足不断增长的现代设备之间的连接需要&#xff0c;它在传统USB标准的基础上提供了更高的电源传输能力和资料…

AI文档比对和Word的“比较”功能有什么区别?

AI文档比对工具的核心区别在于&#xff0c;它超越了Word的纯文本“找不同”&#xff0c;能精准处理扫描件、表格及印章&#xff0c;并将文档审查从被动的文本核对&#xff0c;处理大文档也更为快速及准确。 为什么Word的“比较”功能已经不够用了&#xff1f; 对于许多专业人士…

AI驱动SEO关键词智能进化

内容概要 随着人工智能&#xff08;AI&#xff09;技术的快速演进&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;领域正迎来前所未有的变革。本文核心探讨AI如何驱动SEO关键词的智能进化&#xff0c;重点解析人工智能革新关键词研究与优化策略的机制&#xff0c;包括智能…

基于SpringBoot+MyBatis+MySQL+VUE实现的青年公寓服务平台管理系统(附源码+数据库+毕业论文+部署教程+配套软件)

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;房屋信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…

12.Redis 主从复制

Redis 主从复制Redis 主从复制1. Redis 主从复制架构2. 主从复制实现2.1 主从命令配置2.1.1 启用主从同步2.1.2 查看日志观察同步状态2.1.3 修改 Slave 节点配置文件2.1.4 删除主从同步3. 主从复制故障恢复3.1 Slave 节点故障和恢复3.2 Master 节点故障和恢复3.3 常见主从复制故…

微服务的编程测评系统8-题库管理-竞赛管理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1. 添加题目1.1 service方法1.2 画页面-引入富文本和代码编辑框1.3 子组件中发送请求2. 获取题目详情3. 编辑题目4. 删除题目5. Vue生命周期函数5.1 创建阶段5.2…

基于springboot的学习辅导系统设计与实现

学生&#xff1a;注册登录&#xff0c;学习视频&#xff0c;学习资料&#xff0c;在线交流&#xff0c;系统公告&#xff0c;个人中心&#xff0c;后台管理教师&#xff1a;登录&#xff0c;个人中心&#xff0c;学习视频管理&#xff0c;学习资料管理&#xff0c;签到记录管理…

Kubernetes (K8s) 部署Doris

官网提供yaml地址下载部署 https://doris.apache.org/zh-CN/docs/2.0/install/cluster-deployment/k8s-deploy/install-env/禁用和关闭 swap 在部署 Doris 时&#xff0c;建议关闭 swap 分区。 通过以下命令可以永久关闭 swap 分区。 echo "vm.swappiness 0">>…

AI生成图片工具分享!

CZL在线工具箱近日推出了一款基于Cloudflare Workers AI的免费在线AI图片生成服务。该服务采用**Stable Diffusion XL&#xff08;SDXL&#xff09;**模型&#xff0c;为用户提供高质量、逼真的图像生成体验。 核心特性 全球GPU网络&#xff1a;基于Cloudflare全球分布式GPU网…

Spring Batch的2种STEP定义方式

Spring Batch的2种STEP定义方式 1. 第一种&#xff1a;基于Chunk-Oriented Processing&#xff08;read&#xff0c;process&#xff0c;write&#xff09;形式 适用场景&#xff1a; 大数据量批处理&#xff1a;适合需要分批次读取、处理并写入大量数据的场景&#xff08;如数…

前端JS-调用单删接口来删除多个选中文件

当开发中遇到&#xff1a;服务端没有删除多个文件功能接口&#xff0c;只有单个删除文件功能接口时&#xff0c;会遇到如何多选删除文件效果最佳。await Promise.all(selectedDocPaths.map(async (path) > {try {await fileDelete(path)} catch (err) {throw new Error(删除…

机器学习——过采样(OverSampling),解决类别不平衡问题,案例:逻辑回归 信用卡欺诈检测

下采样&#xff1a;机器学习——下采样&#xff08;UnderSampling&#xff09;&#xff0c;解决类别不平衡问题&#xff0c;案例&#xff1a;逻辑回归 信用卡欺诈检测-CSDN博客 &#xff08;完整代码在底部&#xff09; 解决样本不平衡问题&#xff1a;SMOTE 过采样实战讲解 …

Ettus USRP X440 进行“超短波个人卫星信号的侦查与干扰”任务

结合 Ettus USRP X440 进行“超短波个人卫星信号的侦查与干扰”任务&#xff0c;可以构建一个高性能、灵活可编程的电子对抗系统原型平台。以下是面向科研/工程/军用验证场景的构思和技术文案&#xff1a; &#x1f6f0;️ 项目名称建议&#xff08;可选&#xff09;&#xff1…