resource_retriever 源码详细分析
工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros/resource_retriever
版本:3.1.3(resource_retriever 包),许可证 BSD。
resource_retriever 是一个极小的资源加载库:把多种 URI 协议(package://、file://、http://、ftp:// 等)统一解析,并将文件内容读入内存。最初为 rviz 加载 mesh/纹理 设计,现仍是 ROS 2 可视化栈中 package:// 的底层实现之一。
1. 仓库结构
1 | resource_retriever/ |
特点:核心逻辑不到 200 行;C++ 与 Python 两套 API 语义一致,但实现独立。
2. 架构总览
| 组件 | 职责 |
|---|---|
C++ Retriever |
package:// 解析 + libcurl 下载/读文件 |
Python get() |
同样解析 package://,用 urllib 读取 |
| libcurl_vendor | 系统无 curl 时从源码构建 7.81.0 并安装到 opt/libcurl_vendor |
| ament_index | 将 package://foo/... 映射到 share/foo/... 绝对路径 |
3. 公共 API
3.1 C++:Retriever 与 MemoryResource
1 | struct MemoryResource |
| 类型/方法 | 说明 |
|---|---|
MemoryResource |
shared_ptr<uint8_t> + size,二进制缓冲区 |
Retriever::get(url) |
同步获取资源,失败抛 resource_retriever::Exception |
Exception |
继承 std::runtime_error,格式:Error retrieving file [url]: msg |
头文件中 CURL 仅前向声明,避免在公共头里 #include <curl/curl.h>。
3.2 Python
1 | def get_filename(url, use_protocol=True): |
| 函数 | 返回值 | 说明 |
|---|---|---|
get_filename(url) |
str |
仅做 URI 转换,不读文件 |
get(url) |
bytes |
读入内存 |
Windows 上对 file:// 路径做了特殊处理(反斜杠、/ 前缀)。
4. C++ 核心实现:Retriever::get
4.1 libcurl 全局初始化
1 | class CURLStaticInit |
进程内静态对象保证 curl_global_init / curl_global_cleanup 配对。每个 Retriever 实例持有独立的 curl_easy_init() handle,便于复用连接(注释说明多次访问同一 URL 可保持连接)。
4.2 package:// 解析
1 | MemoryResource Retriever::get(const std::string & url) |
解析规则:
1 | package://<package_name>/<relative_path> |
例如:
1 | package://resource_retriever/test/test.txt |
校验点:
- 必须有
/分隔包名与路径 - 包名不能为空(
package:///test.xml会抛异常) - 包不存在 →
PackageNotFoundError被捕获并转为Exception
4.3 libcurl 读入内存
1 | curl_easy_setopt(curl_handle_, CURLOPT_URL, mod_url.c_str()); |
流程:
curlWriteFunc把数据追加到std::vector<uint8_t>- 成功后拷贝到
shared_ptr<uint8_t[]>(从 ROS 1 时代boost::shared_array迁移而来) - 空文件时
size=0,data为 null
未设置的超时、重定向限制、SSL 校验等 curl 选项均使用 libcurl 默认值。
5. libcurl_vendor 依赖层
resource_retriever 不直接依赖系统 curl,而是通过 libcurl_vendor:
1 | find_package(CURL QUIET) |
libcurl_vendor-extras.cmake.in 逻辑:
- 优先
find_package(CURL) - 失败则用 vendor 构建产物(Linux 走
pkg-config libcurl,Windows 走CURL::libcurltarget) - 导出
libcurl_vendor_LIBRARIES、INCLUDE_DIRS等供下游链接
CMakeLists 中有 TODO:未来可能去掉 vendor,直接使用系统 curl。
6. 支持的 URI 协议
| 协议 | C++ | Python | 说明 |
|---|---|---|---|
package:// |
✅ 先转 file:// |
✅ 同左 | ROS 包 share 目录资源 |
file:// |
✅ libcurl | ✅ urllib | 本地文件 |
http:// / https:// |
✅ libcurl | ✅ urllib | 网络资源 |
ftp:// |
✅ libcurl | ✅ urllib | 理论支持,测试较少 |
不支持:embedded://(rviz Marker 专用,由 rviz 层处理,不经过 resource_retriever)。
7. ROS 2 典型消费者
7.1 rviz:图标与 pixmap
1 | resource_retriever::MemoryResource getResource(const std::string & resource_path) |
loadPixmap("package://rviz_common/icons/...") → QPixmap::loadFromData。
7.2 rviz:mesh 加载(Ogre / Assimp / STL)
1 | Ogre::MeshPtr loadMeshFromResource(const std::string & resource_path) |
Assimp 自定义 IO 系统直接调用 Retriever::get,使主 mesh 与相对路径纹理/子资源都能走同一套 URI 解析:
1 | Assimp::IOStream * Open(const char * file, const char * mode = "rb") override |
ResourceIOStream 在 MemoryResource 上实现 Assimp 的 Read/Seek/Tell,无需落盘临时文件。
7.3 rviz:URDF 纹理
1 | void RobotLink::loadMaterialFromTexture(...) |
7.4 visualization_msgs/Marker
Marker 消息文档明确引用 resource_retriever:
1 | # Texture resource is a special URI ... acceptable to resource retriever |
MESH_RESOURCE 类型 marker 的 mesh_resource 字段常用 package://robot_description/meshes/...。
8. 数据流示例
1 | URDF: package://my_robot/meshes/base.dae |
9. 测试覆盖
C++ gtest (test/test.cpp)
| 用例 | 验证 |
|---|---|
getByPackage |
package://resource_retriever/test/test.txt 内容为 'A' |
http |
http://packages.ros.org/ros.key 非空 |
invalidFiles |
无效 file、缺路径 package、不存在包、空包名 |
Python pytest (test/test.py)
与 C++ 基本对应;差异在于无效包名时 Python 直接抛出 PackageNotFoundError(来自 get_package_share_directory),C++ 则包装为 resource_retriever::Exception。
10. 构建与导出
1 | add_library(${PROJECT_NAME} src/retriever.cpp) |
依赖:
1 | resource_retriever |
11. 设计特点与局限
| 特点 | 说明 |
|---|---|
| URI 统一入口 | 本地包资源与远程 URL 同一 API |
| 零落盘 | 全内存加载,适合嵌入式/只读环境 |
| 轻量 | 无 ROS 节点、无 rclcpp 依赖 |
| 连接复用 | 每 Retriever 实例复用 curl easy handle |
| 局限 | 说明 |
|---|---|
| 同步阻塞 | get() 阻塞直到完成,无 async API |
| 无缓存 | 每次 get() 重新读取;rviz 在上层用 QPixmapCache/Ogre 缓存 |
| 无 ROS 参数/重映射 | 不做 topic/service 式重映射 |
| C++/Python 异常不一致 | 无效包名异常类型不同 |
| 每次调用新建 Retriever | rviz 多处局部创建,未全局共享 handle |
| 双缓冲拷贝 | vector → shared_ptr 数组,多一次 memcpy |
12. 与 ament_index 的关系
1 | package://foo/bar/baz.stl |
与 pluginlib 的 index 机制不同:这里只用 包 share 目录查找,不涉及插件 XML 资源类型。
13. 推荐阅读顺序
- API 面:
retriever.hpp→__init__.py - 核心逻辑:
retriever.cpp的get()与curlWriteFunc - 依赖:
libcurl_vendor/CMakeLists.txt+libcurl_vendor-extras.cmake.in - 集成示例:
- 简单:
rviz_common/load_resource.cpp - mesh:
rviz_rendering/mesh_loader.cpp - Assimp IO:
assimp_loader.cpp中ResourceIOSystem - URDF 纹理:
robot_link.cpp
- 简单:
- 测试:
test/test.cpp理解边界条件
14. 小结
resource_retriever 是 ROS 2 生态中的小型 URI→内存 适配器:package:// 交给 ament_index,其余协议交给 libcurl(C++)或 urllib(Python)。本身不做解析 mesh/图像,只负责按 URI 取字节;rviz、Marker、URDF 可视化在上层解释这些字节。
如果你希望,我可以把本文写入 ros2doc/ros/resource_retriever 源码详细分析.md,或继续分析 AssimpLoader 如何把 MemoryResource 转成 Ogre Mesh 的完整链路。
正在加载留言…