ROS2软件调用架构和机制解析:Publisher创建
术语
- DDS (Data Distribution Service): 用于实时系统的数据分发服务标准,是ROS 2底层通信的基础
- RMW (ROS Middleware): ROS中间件接口,提供与具体DDS实现无关的抽象API
- QoS (Quality of Service): 服务质量策略,控制通信的可靠性、历史记录、耐久性等属性
- 符号解析: 动态库加载过程中,查找和绑定函数指针的机制
1. 架构概述
ROS2采用分层设计,通过多层抽象实现高度灵活性和可插拔性。其调用链如下:
应用层(开发人员) → rclcpp(C++客户端库) → rcl(C语言客户端库) → rmw(中间件接口) → rmw_implementation(动态加载层) → 具体DDS实现
2. 发布者创建流程详解
2.1 用户代码层(rclcpp::Node::create_publisher)
通常使用以下代码创建发布者:
auto node = std::make_shared<rclcpp::Node>("publisher_node");
auto publisher = node->create_publisher<std_msgs::msg::String>("topic_name", 10); // 队列深度为10
2.2 rclcpp层(Node类)
在node.hpp文件中对各模板函数进行声明,函数的具体实现在node_impl.hpp文件中进行定义。
class Node : public std::enable_shared_from_this<Node>
{template<typename MessageT,typename AllocatorT = std::allocator<void>,typename PublisherT = rclcpp::Publisher<MessageT, AllocatorT>>std::shared_ptr<PublisherT>create_publisher(const std::string & topic_name,const rclcpp::QoS & qos,const PublisherOptionsWithAllocator<AllocatorT> & options =PublisherOptionsWithAllocator<AllocatorT>());
}
node_impl.hpp文件中,node->create_publisher 调用一系列rclcpp函数:
template<typename MessageT, typename AllocatorT, typename PublisherT>
std::shared_ptr<PublisherT>
Node::create_publisher(const std::string & topic_name,const rclcpp::QoS & qos,const PublisherOptionsWithAllocator<AllocatorT> & options)
{return rclcpp::create_publisher<MessageT, AllocatorT, PublisherT>(*this,extend_name_with_sub_namespace(topic_name, this->get_sub_namespace()),qos,options);
}
该方法调用create_publisher.hpp文件的node_topics_interface->create_publisher创建类型特定的工厂
template<typename MessageT,typename AllocatorT = std::allocator<void>,typename PublisherT = rclcpp::Publisher<MessageT, AllocatorT>,typename NodeParametersT,typename NodeTopicsT>
std::shared_ptr<PublisherT>
create_publisher(NodeParametersT & node_parameters,NodeTopicsT & node_topics,const std::string & topic_name,const rclcpp::QoS & qos,const rclcpp::PublisherOptionsWithAllocator<AllocatorT> & options = (rclcpp::PublisherOptionsWithAllocator<AllocatorT>())
)
{auto node_topics_interface = rclcpp::node_interfaces::get_node_topics_interface(node_topics);const rclcpp::QoS & actual_qos = options.qos_overriding_options.get_policy_kinds().size() ?rclcpp::detail::declare_qos_parameters(options.qos_overriding_options, node_parameters,node_topics_interface->resolve_topic_name(topic_name),qos, rclcpp::detail::PublisherQosParametersTraits{}) :qos;// Create the publisher.auto pub = node_topics_interface->create_publisher(topic_name,rclcpp::create_publisher_factory<MessageT, AllocatorT, PublisherT>(options),actual_qos);// Add the publisher to the node topics interface.node_topics_interface->add_publisher(pub, options.callback_group);return std::dynamic_pointer_cast<PublisherT>(pub);
}
node_topics_interface->create_publisher实际调用node_topics.cpp文件的NodeTopics::create_publisher非模板方法,该方法使用publisher_factory.hpp文件中工厂的create_typed_publisher函数创建实际的发布者,创建的发布者进行后期初始化设置。
rclcpp::PublisherBase::SharedPtr
NodeTopics::create_publisher(const std::string & topic_name,const rclcpp::PublisherFactory & publisher_factory,const rclcpp::QoS & qos)
{// Create the MessageT specific Publisher using the factory, but return it as PublisherBase.return publisher_factory.create_typed_publisher(node_base_, topic_name, qos);
}
而create_typed_publisher的实现如下:
template<typename MessageT, typename AllocatorT, typename PublisherT>
PublisherFactory
create_publisher_factory(const rclcpp::PublisherOptionsWithAllocator<AllocatorT> & options)
{PublisherFactory factory {// factory function that creates a MessageT specific PublisherT[options](rclcpp::node_interfaces::NodeBaseInterface * node_base,const std::string & topic_name,const rclcpp::QoS & qos) -> std::shared_ptr<PublisherT>{auto publisher = std::make_shared<PublisherT>(node_base, topic_name, qos, options);// This is used for setting up things like intra process comms which// require this->shared_from_this() which cannot be called from// the constructor.publisher->post_init_setup(node_base, topic_name, qos, options);return publisher;}};// return the factory now that it is populatedreturn factory;
}
创建特定的Publisher时,publisher.hpp文件内部会转到具体的Publisher构造函数:
Publisher(rclcpp::node_interfaces::NodeBaseInterface * node_base,const std::string & topic,const rclcpp::QoS & qos,const rclcpp::PublisherOptionsWithAllocator<AllocatorT> & options)
: PublisherBase(node_base,topic,rclcpp::get_message_type_support_handle<MessageT>(),options.template to_rcl_publisher_options<MessageT>(qos)),options_(options),published_type_allocator_(*options.get_allocator()),ros_message_type_allocator_(*options.get_allocator())
{// 初始化内存分配器和事件回调
}
PublisherBase构造函数则进行RCL层的publisher初始化
PublisherBase::PublisherBase(rclcpp::node_interfaces::NodeBaseInterface * node_base,const std::string & topic,const rosidl_message_type_support_t & type_support,const rcl_publisher_options_t & publisher_options)
: node_base_(node_base),topic_(topic)
{// 创建rcl_publisher_t实例publisher_handle_ = std::shared_ptr<rcl_publisher_t>(new rcl_publisher_t, [node_handle](rcl_publisher_t * publisher) {if (rcl_publisher_fini(publisher, node_handle) != RCL_RET_OK) {RCLCPP_ERROR(/* ... */);}delete publisher;});*publisher_handle_.get() = rcl_get_zero_initialized_publisher();// 关键调用:初始化RCL层publisherrcl_ret_t ret = rcl_publisher_init(publisher_handle_.get(),node_base->get_rcl_node_handle(),&type_support,topic.c_str(),&publisher_options);if (ret != RCL_RET_OK) {// 错误处理}
}
2.3 rcl层
rcl_publisher_init 函数进一步处理:
rcl_ret_t
rcl_publisher_init(rcl_publisher_t * publisher,const rcl_node_t * node,const rosidl_message_type_support_t * type_support,const char * topic_name,const rcl_publisher_options_t * options
)
{// 参数验证和初始化// 向RMW层请求创建发布者// rmw_handle为rmw_publisher_t *类型publisher->impl->rmw_handle = rmw_create_publisher(rcl_node_get_rmw_handle(node),type_support,remapped_topic_name,&(options->qos),&(options->rmw_publisher_options));// 错误处理与返回值设置
}
2.4 rmw层(通过rmw_implementation)
此处进入rmw_implementation包的functions.cpp中,其中rmw_create_publisher为rmw.h文件中定义的接口函数:
RMW_INTERFACE_FN(rmw_create_publisher,rmw_publisher_t *, nullptr,5, ARG_TYPES(const rmw_node_t *, const rosidl_message_type_support_t *, const char *,const rmw_qos_profile_t *, const rmw_publisher_options_t *))
这个宏展开后生成:
#define RMW_INTERFACE_FN(name, ReturnType, error_value, _NR, ...) \void * symbol_ ## name = nullptr; \ReturnType name(EXPAND(ARGS_ ## _NR(__VA_ARGS__))) \{ \CALL_SYMBOL( \name, ReturnType, error_value, ARG_TYPES(__VA_ARGS__), \EXPAND(ARG_VALUES_ ## _NR(__VA_ARGS__))); \}
CALL_SYMBOL宏进一步展开:
#define CALL_SYMBOL(symbol_name, ReturnType, error_value, ArgTypes, arg_values) \if (!symbol_ ## symbol_name) { \/* only necessary for functions called before rmw_init */ \//获取库中的函数符号symbol_ ## symbol_name = get_symbol(#symbol_name); \} \if (!symbol_ ## symbol_name) { \/* error message set by get_symbol() */ \return error_value; \} \typedef ReturnType (* FunctionSignature)(ArgTypes); \// 根据函数地址,调用加载的函数FunctionSignature func = reinterpret_cast<FunctionSignature>(symbol_ ## symbol_name); \return func(arg_values);
get_symbol 函数获取函数符号:
void *
get_symbol(const char * symbol_name)
{try {return lookup_symbol(get_library(), symbol_name);} catch (const std::exception & e) {RMW_SET_ERROR_MSG_WITH_FORMAT_STRING("failed to get symbol '%s' due to %s",symbol_name, e.what());return nullptr;}
}
2.5 加载和符号查找
关键的lookup_symbol函数负责从已加载的库中查找符号:
void *
lookup_symbol(std::shared_ptr<rcpputils::SharedLibrary> lib, const std::string & symbol_name)
{if (!lib) {if (!rmw_error_is_set()) {RMW_SET_ERROR_MSG("no shared library to lookup");} // else assume library loading failedreturn nullptr;}if (!lib->has_symbol(symbol_name)) {try {std::string library_path = lib->get_library_path();RMW_SET_ERROR_MSG_WITH_FORMAT_STRING("failed to resolve symbol '%s' in shared library '%s'",symbol_name.c_str(), library_path.c_str());} catch (const std::exception & e) {RMW_SET_ERROR_MSG_WITH_FORMAT_STRING("failed to resolve symbol '%s' in shared library due to %s",symbol_name.c_str(), e.what());}return nullptr;}// 返回找到的函数指针return lib->get_symbol(symbol_name);
}
get_library()函数负责加载RMW实现库:
std::shared_ptr<rcpputils::SharedLibrary> get_library()
{if (!g_rmw_lib) {// 懒加载策略 - 首次使用时加载g_rmw_lib = load_library();}return g_rmw_lib;
}
2.6 具体DDS实现
在rmw_publisher.cpp文件中,调用rmw_fastrtps_cpp::create_publisher函数进行publisher创建。最终,通过符号解析得到的函数指针指向特定DDS实现(如FastDDS、CycloneDDS)提供的具体rmw_create_publisher实现:
// 在FastDDS中的实现示例
rmw_publisher_t *
rmw_fastrtps_cpp::create_publisher(const CustomParticipantInfo * participant_info,const rosidl_message_type_support_t * type_supports,const char * topic_name,const rmw_qos_profile_t * qos_policies,const rmw_publisher_options_t * publisher_options,bool keyed,bool create_publisher_listener)
{rmw_publisher_t * publisher = rmw_fastrtps_cpp::create_publisher(participant_info,type_supports,topic_name,qos_policies,publisher_options,false,true);// 创建并返回rmw_publisher_t结构
}rmw_publisher_t *
rmw_fastrtps_cpp::create_publisher(const CustomParticipantInfo * participant_info,const rosidl_message_type_support_t * type_supports,const char * topic_name,const rmw_qos_profile_t * qos_policies,const rmw_publisher_options_t * publisher_options,bool keyed,bool create_publisher_listener)
{eprosima::fastdds::dds::Publisher * publisher = participant_info->publisher_;...
}
3. 层级交互总结
- rclcpp层:提供面向对象的友好API,管理C++对象生命周期
- rcl层:提供C语言API,处理资源分配、错误处理和参数验证
- rmw层:定义中间件抽象接口,与具体DDS无关
- rmw_implementation层:
- 使用环境变量决定加载哪个RMW实现
- 通过动态符号查找机制(
lookup_symbol)获取函数指针 - 通过函数指针转发调用到实际DDS实现
- 具体DDS实现:执行真正的DDS操作,与网络通信
4. 实际应用示例
在ROS 2系统中,可以通过环境变量轻松切换底层DDS实现:
# 使用FastDDS (默认)
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp# 使用CycloneDDS
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp# 使用Connext DDS
export RMW_IMPLEMENTATION=rmw_connextdds# 查看当前使用的RMW实现
ros2 doctor --report | grep middleware
相关文章:
ROS2软件调用架构和机制解析:Publisher创建
术语 DDS (Data Distribution Service): 用于实时系统的数据分发服务标准,是ROS 2底层通信的基础RMW (ROS Middleware): ROS中间件接口,提供与具体DDS实现无关的抽象APIQoS (Quality of Service): 服务质量策略,控制通信的可靠性、历史记录、…...
【落羽的落羽 C++】C++入门基础·其之一
文章目录 一、C简介1. C的发展历史2. C参考文档 二、namespace命名空间1. C语言的一个缺陷2. namespace3. 命名空间的使用3.1 命名空间成员访问3.2 using展开 一、C简介 1. C的发展历史 C起源于1979年的贝尔实验室,Bjarne Stroustrup(本贾尼博士&#…...
docker使用代理的简单配置
1准备代理服务器 准备代理服务器,例如192.168.120.168:52209 配置docker.service文件 查看service文件的位置 systemctl status docker 编辑service文件 vim /usr/lib/systemd/system/docker.service 添加代理配置 ... [Service] Environment"HTTP_PROXY…...
每日一题-设计食物评分系统,哈希表的有效使用
本题出自LeetCode2353.设计食物评分系统,连着一星期都是设计类的题目哈 题目 设计一个支持下述操作的食物评分系统: 修改 系统中列出的某种食物的评分。返回系统中某一类烹饪方式下评分最高的食物。 实现 FoodRatings 类: FoodRatings(Strin…...
大模型应用:多轮对话(prompt工程)
概述 在与大型语言模型(如ChatGPT)交互的过程中,我们常常体验到与智能助手进行连贯多轮对话的便利性。那么,当我们开启一个新的聊天时,系统是如何管理聊天上下文的呢? 一、初始上下文的建立 1. 创建新会…...
WSDM24-因果推荐|因果去偏的可解释推荐系统
1 动机 可解释推荐系统(ERS)通过提供透明的推荐解释,提高用户信任度和系统的说服力,如下图所示,然而: 1:现有工作主要关注推荐算法的去偏(流行度偏差),但未显…...
VScode在Windows11中配置MSVC
因为MSVC编译器在vs当中,所以我们首先要安装vs的一部分组件。如果只是需要MSVC的话,工作负荷一个都不需要勾选,在单个组件里面搜索MSVC和windows11 SDK,其中一个是编译器,一个是头文件然后右下角安装即可。搜索Develop…...
数据库基础二(数据库安装配置)
打开MySQL官网进行安装包的下载 https://www.mysql.com/ 接着找到适用于windows的版本 下载版本 直接点击下载即可 接下来对应的内容分别是: 1:安装所有 MySQL 数据库需要的产品; 2:仅使用 MySQL 数据库的服务器; 3&a…...
cuda-12.4.0 devel docker 中源码安装 OpenAI triton
1,准备 docker 容器 下载docker image: $ sudo docker pull nvidia/cuda:12.6.2-devel-ubuntu20.04 创建容器: sudo docker run --gpus all -it --name cuda_LHL_01 -v /home/hongleili/ex_triton/tmp1:/root/ex_triton/tmp1 nvidia/cuda:12.6…...
doris: Hive Catalog
通过连接 Hive Metastore,或者兼容 Hive Metatore 的元数据服务,Doris 可以自动获取 Hive 的库表信息,并进行数据查询。 除了 Hive 外,很多其他系统也会使用 Hive Metastore 存储元数据。所以通过 Hive Catalog,我们不…...
【LeetCode】131.分割回文串
目录 题目描述输入输出示例及数据范围思路C 实现 题目描述 这道题目来自 LeetCode 131. 分割回文串。 题目描述如下: 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。 输入输出示例及数据…...
JeeWMS graphReportController.do SQL注入漏洞复现(CVE-2025-0392)
免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 0x0…...
基于Python+django+mysql旅游数据爬虫采集可视化分析推荐系统
2024旅游推荐系统爬虫可视化(协同过滤算法) 基于Pythondjangomysql旅游数据爬虫采集可视化分析推荐系统 有文档说明 部署文档 视频讲解 ✅️基于用户的协同过滤推荐算法 卖价就是标价~ 项目技术栈 Python语言、Django框架、MySQL数据库、requests网络爬虫…...
我的工作经历
主要说一下毕业工作大半年了一些心得与想法。 首先是因为本科不好的原因,单2硕士找了一个国企(其实应该说是央企)。也幸好找的是央企,后续工作基本上没有强度,不然后期神经衰弱抑郁症家里乱七八糟催婚的事情能把人逼疯…...
筑牢安全防线:工商业场所燃气泄漏防护新方案
燃气安全是企业经营不可逾越的生命线。在餐饮后厨、化工车间、酒店锅炉房等场所,可燃气体一旦泄漏,极易引发严重事故。如何实现精准监测、快速响应,成为工业及商业领域安全管理的核心诉求。旭华智能深耕安全监测领域,推出的工业及…...
基于STM32的智能停车场管理系统
1. 引言 传统停车场管理存在车位利用率低、停车体验差等问题,难以满足现代城市停车需求。本文设计了一款基于STM32的智能停车场管理系统,通过车位状态实时监测、智能导航与无感支付技术,实现停车资源的高效利用与用户服务的全面升级。 2. 系…...
MacBook 终端中使用 vim命令
在 MacBook 终端中使用 vim 编辑器时,以下是一些常用命令和操作指南: 1. 基本操作 启动 vim vim 文件名 # 打开或创建文件退出 vim 保存并退出: 按 Esc,然后输入 :wq,按 Enter。 不保存退出: 按 Esc&am…...
VoIP之SBC(会话边界控制器)
SBC(Session Border Controller,会话边界控制器)是一种在VoIP通信网络中的重要设备,用于连接处理会话边界,核心功能包含信令代理/媒体代理、网络NAT穿越、防火墙、QoS等。 经典案例 关键说明 用于客户端和核心业务服务器的互联互通支持IP接入控…...
threejs:document.createElement创建标签后css设置失效
vue3threejs,做一个给模型批量CSS2D标签的案例,在导入模型的js文件里,跟着课程写的代码如下: import * as THREE from three; // 引入gltf模型加载库GLTFLoader.js import { GLTFLoader } from three/addons/loaders/GLTFLoader.…...
安装2018版本的petalinux曲折经历
具体操作步骤 1.安装VMware Workstation15.5的虚拟机2.安装Ubuntu16.04.43.配置Ubuntu的环境1.可以复制粘贴的指令2.安装vim 4.准备安装petalinux1.先配置petalinux的安装环境2.替换镜像源1.备份原始的软件源2.从以下镜像点找到合适自己系统版本的源3.执行替换镜像源1.打开源文…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
第八部分:阶段项目 6:构建 React 前端应用
现在,是时候将你学到的 React 基础知识付诸实践,构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段,你可以先使用模拟数据,或者如果你的后端 API(阶段项目 5)已经搭建好,可以直接连…...
C++--string的模拟实现
一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现,其目的是加强对string的底层了解,以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量,…...
