ROS2-DDS及共享内存应用

1. DDS简介

DDS是基于订阅-发布的模型,但是去中心化,避免中间服务器出现问题而导致各个节点的通讯瘫痪,取而代之的是DataBus.FastDDS是DDS的一种以C++的具体实现
ROS2为了更加专注于应用层,也引入DDS通讯,同时为了兼容多种DDS实现,而且避免应用层与通讯层的耦合,增加了中间件RMW(Middleware).
ros2架构
ros2支持的RMW

名称协议RMW实现状态
eProsima Fast RTPSApache 2rmw_fastrtps_cpp完全支持. 默认的RMW. 已经打包在发布的文件中.
RTI Connextcommercial, researchrmw_connext_cpp完全支持. 需要从源码编译支持.
RTI Connext (dynamic implementation)commercial, researchrmw_connext_dynamic_cpp停止支持. alpha 8.* 之前版本完全支持
RTI Connext (dynamic implementation)commercial, researchrmw_connext_dynamic_cpp停止支持. alpha 8.* 之前版本完全支持
PrismTech OpenspliceLGPL (only v6.4), commercialrmw_opensplice_cpp停止支持. alpha 8.* 之前版本完全支持
OSRF FreeRTPSApache 2部分支持. 开发暂停.

2. Fast-DDS

2.1 DDS层

DDS层

DDS实体及其说明和功能:

  • 域:各个节点必须处于同一域(domain)才能进行交互
  • 域参与者(DomainParticipant):包含其他DDS实体(如发布者/订阅者/主题等)
  • 发布者:定义信息生成对象及其属性;
  • 数据编写器(DataWrite):负责发布消息的实体
  • 数据编写历史(DataWriteHistory)
  • 订阅者:定义信息消耗对象及其属性;
  • 数据读取器(DataReader):订阅接收发布者的消息
  • 数据读取器历史(DataReaderHistory)
  • 话题:绑定发布者和订阅者

2.2 RTPS 层

RTPS
RTPS(real time publish subscribe)用于开发DDS应用,是发布-订阅通信的中间件

RTPS层主要涉及4个对象:

  • RTPSDomain:DDS域与RPTS协议的扩展
  • RTPSParticipant:包含其他RTPS对象的对象
  • RTPSWrite:生成消息的对象,读取DataWriteHistory中写入的更改,病传输给所有与它匹配所有RTPSReaders
  • RTPSReader:消息接收对象,它将RTPSWriteer报告的更改写入DataReaderHistory

DDS层与RTPS的关系如下图所示
DDS层与RTPS的关系

2.3 传输层

Fast DDS支持各种传输协议实现应用程序.

  • UDPv4/UDPv6
  • TCPv4/TCPv6
  • 共享内存(SHM)

默认情况下,当Participant创建时,将自动配置两个传输通道:

  • SHM:用来同一个机器上的参与者通信
  • UDPv4:不同机器上的参与者通信

2.4 Fast DDS-Gen

Fast DDS-Gen是一个Java应用程序,根据 接口描述语言(interface description language, IDL)文件生以下内容(类似protobuf根据.proto生成相应语言的源码):

  • 自定义数据的头文件.h
  • 自定义数据的定义文件.cxx
  • 自定义数据的序列化和反序列化的定义.cxx
  • 自定义数据的序列化和反序列化的头文件.h

Writing a simple C++ publisher and subscriber application利用Fast DDS-Gen定义主题数据,并实现 发布者和订阅者

2.5 安装

Linux参考文档

3. 基于Fast-DDS的共享内存

SHM参考文章
共享内存传递消息的特点:

  • 大消息支持:网络协议需要对数据分段,增加通信开销.而SHM传输任意大小的消息,其大小的唯一限制就是系统的内存限制;
  • 减少内存副本:其它协议分发到各个节点都需要拷贝一份数据,而SHM传输可以直接与所有目标节点共享相同的缓冲区;
  • 更少的系统开销:初始化设置完成后,共享内存传输需要更少的系统调用

SHM时序图

注意事项
In case that several transports are enabled, the discovery traffic is always performed using the UDP/TCP transport, even if the SHM transport is enabled in both participants running in the same machine. This may cause discovery issues if one or several of the participants only has SHM enabled and other participants use some other transport at the same time. Also, when two participants on the same machine have SHM transport enabled, the user data communication between them is automatically performed by SHM transport only. The rest of the enabled transports are not used between those two participants.

3.1 段

segment段是从不同进程访问的共享内存块.每个段有一个segmentID,一个16byte组成的UUID,用于唯一标识每个共享内存段

3.2 段缓冲区

  • 段缓冲区即在共享内存段中分配的缓冲区,domain Participant将消息放置于段缓冲区
  • 段缓冲区描述符 由2部分组成
    • segmentID:指明哪个段
    • segmentOffset:与段基址的偏移量
  • 共享内存的传输实质是:data writer发送段缓冲区data Reader

3.3 端口

  • 每个配置了共享内存的domain Participant都会创建一个端口以接收 缓冲区描述;
  • 此端口Discovery期间共享,以告知其它Participant,其它Participant就可以根据这个端口与其进行通讯

3.4 SHM的实现过程

SHM的实例
上图展示的场景是域中有3个参与者P1/P2/P3;

  1. 发现(Discovery)阶段: 所有参与者做的第一件事是打开共享端口0,并向端口0发送自己另一个端口号.通俗的描述就是(我自己的理解):端口0和另一个端口的关系相当于手机号和114的关系,我们注册了一个手机号码,相当于告知114(端口0, 运营商) 自己使用(侦听)某个手机号码(另一个端口),别人可以通过114(端口0)知道自己与号码(另一个端口)的绑定关系.发现阶段通过多播(multicast)实现
  2. 发现阶段之后:各个参与者以单播形式向指定端口推送消息(段缓冲区描述符),上图P1向P2和P3推送消息1c只需要分别向port2和port3发送descr_1c(消息内容本身不需要拷贝!)

3.5 SHM应用实例

源码实例参见Fast-DDS源码中的目录:Fast-DDS/examples/cpp/dds/HelloWorldExampleSharedMem/
本例子仅介绍如何使用fast-DDS的共享内存,而如果需要和ros节点实现通讯,可以采用非ros进程调用fast-DDS的库与ros节点的DDS通讯.具体可以参考链接,它强调两点:

  • 消息数据使用 fastddsgen *.idl生成时需要添加-typeros2
  • 创建topic时需要添加前缀rt/

遗憾的是,该方案我没有试验成功.为此下文将介绍另一个方案.

4. ros2节点配置使用SHM

参考例程:https://github.com/ZhenshengLee/ros2_shm_msgs

使用共享内存的条件:

  • 运行的不同节点在同一台设备上;
  • ROS版本: >= galactic;
  • 指定xml文件;
  • 自定义消息;
  • 修改程序:发布时使用特定的接口获取msg的内存空间;

4.1 准备xml文件

xml与使用的中间件有关,下面提供fastDDS中间的xml

<?xml version="1.0" encoding="UTF-8"?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
    <transport_descriptors>
        <transport_descriptor>
            <transport_id>UDP_transport</transport_id>
            <type>UDPv4</type>
            <maxInitialPeersRange>10</maxInitialPeersRange>
        </transport_descriptor>
    </transport_descriptors>

    <participant profile_name="participant_profile_ros2" is_default_profile="true">
        <rtps>
            <name>profile_for_ros2_context</name>
            <userTransports>
                <transport_id>UDP_transport</transport_id>
            </userTransports>
            <useBuiltinTransports>false</useBuiltinTransports>

            <builtin>
                <metatrafficUnicastLocatorList>
                    <locator />
                </metatrafficUnicastLocatorList>
                <initialPeersList>
                    <locator>
                        <udpv4>
                            <address>127.0.0.1</address>
                        </udpv4>
                    </locator>
                </initialPeersList>
            </builtin>
        </rtps>
    </participant>

    <data_writer profile_name="default publisher profile" is_default_profile="true">
        <qos>
            <publishMode>
                <kind>ASYNCHRONOUS</kind>
            </publishMode>
            <data_sharing>
                <kind>AUTOMATIC</kind>
            </data_sharing>
        </qos>
        <historyMemoryPolicy>PREALLOCATED_WITH_REALLOC</historyMemoryPolicy>
    </data_writer>

    <data_reader profile_name="default subscription profile" is_default_profile="true">
        <qos>
            <data_sharing>
                <kind>AUTOMATIC</kind>
            </data_sharing>
        </qos>
        <historyMemoryPolicy>PREALLOCATED_WITH_REALLOC</historyMemoryPolicy>
    </data_reader>
</profiles>

4.2 自定义消息

msg中除int/char等基本类型,其它都应该用数组存储,该消息编译后会变成std::array类型;

以图片为例定义以下的消息,注意分辨率和数组的大小是否合适

# This message contains an uncompressed image
# (0, 0) is at top-left corner of image

# Two-integer timestamp that is expressed as seconds and nanoseconds.
builtin_interfaces/Time stamp

# Transform frame with which this data is associated.
## 需要提前自定义String
[self_define_msg_package]/String frame_id

uint32 height                # image height, that is, number of rows
uint32 width                 # image width, that is, number of columns

uint8[1048576] data          # actual matrix data, size is (step * rows)

uint32 DATA_MAX_SIZE=1048576

消息被编译后DATA_MAX_SIZE生成对应大小的std::array数组

(上面例子只是最基本的数据,还可以添加图片编码格式等信息)

4.3 修改程序

4.3.1 发布者

  1. 通过borrow_loaned_message获取rclcpp::LoanedMessage<Topic>对象;
  2. rclcpp::LoanedMessage<Topic>对象的成员函数get()获取真正消息对象;
  3. 另外,如果使用的是string/mat/pcl这些类型的数据,因为使用的是std::array作存储,所以要根据实际作赋值操作;
  4. 推送消息时使用std::move以调用共享内存形式的发布重载函数publish(rclcpp::LoanedMessage<MessageT, AllocatorT> && loaned_msg)
//例如
using Topic = keystar_msgs::msg::Image;

//发布对象无需特别设置qos
publisher_ = this->create_publisher<Topic>("shm_topic", 10);
//或者下面也行
//rclcpp::QoS qos(rclcpp::KeepLast(10));
//publisher_ = this->create_publisher<Topic>("shm_topic", qos);

auto loandMsg = publisher_->borrow_loaned_message();
Topic& msg = loandMsg.get();

//赋值操作
cv::Mat image;
memcpy(msg.data.data(), image.data, img_size);
//...其它赋值操作

publisher_->publish(std::move(loandMsg));

4.3.2 订阅者

订阅者的程序除了需要对特殊的数据类型进行读取操作,不需要其它特殊处理

创建cv::Mat时如不需改动图片数据时,切记使用浅拷贝,不然就失去共享内存的意义!详细实现参考源码

4.4 启动节点

  1. 指定xml文件
  • 通过环境变量指定ros的RMW
  • 通过环境变量指定上面的xml配置
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export FASTRTPS_DEFAULT_PROFILES_FILE=[path]/shm_fastdds.xml
  1. 运行节点

  2. 检查是否共享内存是否生效

# check if shm-transport
ls /dev/shm/fastrtps_
# check if data-sharing
ls /dev/shm/fast_datasharing*

5. Q&A

  1. 能通过ipcs -m查询是否创建共享内存吗?
    经测试不能
ipcs -m

## output:
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
  1. 除了/dev/shm/fast_datasharing*说明创建了,还有其它手段验证码
    还可以通过直接打印所传递消息的地址,注意需要打印物理地址,直接取地址符的话,即使同一个物理地址都因为有不同的映射表导致虚拟地址不一

虚拟地址->物理地址 的方法参考:
如何在应用层获取物理地址

  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ROS2 Web Bridge是一个开源的软件包,旨在使ROS2(Robot Operating System 2)与Web端进行通信和交互。它提供了一种简单而强大的方式,通过WebSocket协议将ROS2系统中的数据传输到Web浏览器。 ROS2 Web Bridge允许Web开发人员使用常见的Web技术(例如JavaScript)直接与ROS2系统进行交互。它提供了一个轻量级的接口,可以订阅和发布ROS2主题,并在Web浏览器中实时显示传感器数据、控制机器人等。 此外,ROS2 Web Bridge还支持ROS2服务和动作。它允许Web应用程序在Web端调用ROS2服务,从而实现与ROS2节点的双向通信。通过一个用户友好的Web界面,用户可以发送控制命令给机器人,执行任务并获得实时反馈。 ROS2 Web Bridge的主要优点之一是其跨平台性。它基于WebSocket协议,因此可以在不同的操作系统和设备上使用,无论是在PC端还是移动设备上。 此外,ROS2 Web Bridge还支持认证和授权机制,以确保通信安全。这对于确保只有被授权的用户可以访问和控制ROS2系统非常重要。 总的来说,ROS2 Web Bridge为ROS2系统提供了一种简便而强大的方式,使Web开发人员能够与ROS2系统进行交互。它扩展了ROS2的功能,使得机器人开发更加灵活和可视化。 ### 回答2: ros2-web-bridge是一种用于在ROS 2和Web应用程序之间进行通信的桥接工具。ROS 2是机器人操作系统的第二代版本,而Web应用程序是通过Web浏览器访问的应用程序。 ros2-web-bridge有几个主要功能。首先,它允许ROS 2中的节点直接与通过Web浏览器访问的Web应用程序进行通信。这使得在Web界面上实时监控和控制ROS 2系统变得更加容易。例如,可以使用ros2-web-bridge将传感器数据从ROS 2节点发送到Web应用程序,以便在Web界面上实时显示传感器数据。同时,还可以将来自Web界面的用户输入发送到ROS 2节点,以便远程控制机器人或系统。 其次,ros2-web-bridge还提供了一些工具和API,用于将ROS 2中的消息和服务转换为Web格式。这使得可以轻松地在ROS 2和Web应用程序之间进行数据传输和交互。它支持使用ROS 2的套接字和JSON进行通信,并提供了将消息和服务转换为JSON格式以及反向转换的功能。这样,ROS 2节点可以与通过Web浏览器访问的Web应用程序进行无缝通信。 总而言之,ros2-web-bridge为ROS 2和Web应用程序之间的通信提供了一个便捷的桥梁。它简化了ROS 2系统与Web界面之间的集成,并提供了实时数据传输和远程控制的能力。这对于构建基于ROS 2的机器人或系统的开发者和使用者来说是非常方便的工具。 ### 回答3: ros2-web-bridge是一个用于将ROS 2和Web技术进行连接的工具。它提供了一个桥接器,使得可以通过Web浏览器与ROS 2通信。这个工具是建立在ROS 2和Web Socket之间的通信基础上的。 通过ros2-web-bridge,我们可以在Web浏览器中实时地订阅和发布ROS 2的消息。这使得我们可以通过Web界面来控制ROS 2的机器人,或者将ROS 2的数据可视化展示出来。这对于远程监控、远程操作和数据可视化都非常有用。 ros2-web-bridge使用ROS 2提供的接口来与ROS 2系统进行通信。它将ROS 2的消息转换为适用于Web Socket的格式,并在浏览器和ROS 2之间建立起适配的连接。通过这种方式,Web界面就能够与ROS 2系统进行实时的双向通信。 ROS 2中的消息传递方式是异步的,而Web浏览器通常使用同步的方式进行通信。ros2-web-bridge通过在ROS 2和Web Socket之间进行适配和转换,使得二者能够协同工作。这意味着ROS 2中的数据可以通过ros2-web-bridge传输到Web浏览器,并在该浏览器中进行处理和展示。 总的来说,ros2-web-bridge是一个功能强大的工具,它架起了ROS 2和Web技术之间的桥梁。它使得我们可以通过Web浏览器来与ROS 2进行通信,进而实现远程操作和数据可视化的目的。通过ros2-web-bridge,我们可以更加灵活和方便地利用ROS 2的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值