[翻译] Iceoryx的核心功能介绍

1 概要

本节翻译自: iceoryx/doc/website/getting-started/overview.md
本节涵盖了Eclipse Iceoryx的核心功能,并旨在提供快速介绍如何设置Iceoryx应用程序。

要使用Iceoryx设置一组应用程序(即_iceoryx系统_),应用程序需要初始化运行时并创建通信参与者,如_publishers_和_subscribers_或_clients_和_servers_。发布者发送特定主题的数据,可以被同一主题的订阅者接收。服务器在一个主题上等待来自客户端的请求并响应这些请求。为了使这些参与者正常运行,中间件守护进程RouDi必须在运行。

但在详细说明之前,让我们从一个简单的发布-订阅示例开始。

1.1 第一个示例

我们需要为每个应用程序创建一个唯一名称的运行时,以便与RouDi进行通信。

iox::runtime::PoshRuntime::initRuntime("some_unique_name");

现在这个应用程序已经准备好与RouDi通信,我们可以定义要发送的数据类型。

struct CounterTopic
{
    uint32_t counter;
};

然后我们创建一个发布者来提供我们的CounterTopic。

iox::popo::Publisher<CounterTopic> publisher({"Group", "Instance", "CounterTopic"});

现在我们可以使用发布者发送数据。

auto result = publisher.loan();
if(result.has_value())
{
    auto& sample = result.value();
    sample->counter = 30;
    sample.publish();
}
else
{
    // 处理错误
}

这里的result是一个iox::expected,因此我们可能会遇到错误。这可能发生在我们尝试借用太多样本并耗尽内存时。我们必须处理这种潜在错误,因为expected类带有nodiscard关键字。这意味着如果我们不处理它,会收到警告(在严格模式下构建时是错误)。我们也可以用IOX_DISCARD_RESULT显式丢弃它,但不推荐。如果您想了解更多关于iox::expected的信息,请参阅如何在Iceoryx中返回可选和错误值

让我们创建一个对应的订阅者。

iox::popo::Subscriber<CounterTopic> subscriber({"Group", "Instance", "CounterTopic"});

现在我们可以使用订阅者接收数据。为简化起见,我们假设定期检查新数据。也可以使用WaitSetListener来显式等待数据。接收数据的代码是相同的,唯一的区别是检查数据前唤醒的方式。

while (keepRunning)
{
    // 等待新数据(可以通过定期睡眠和唤醒或WaitSet通知)

    auto result = subscriber.take();

    if(result.has_value())
    {
        auto& sample = result.value();
        uint32_t counter = sample->counter;
        // 处理数据
    }
    else
    {
        // 处理错误
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

通过调用take我们得到了一个expected,因此我们必须处理潜在的错误。

就是这样。我们已经创建了第一个简单的Iceoryx示例。这里可以找到更多示例,展示如何使用Iceoryx并详细描述我们的API。

现在我们有了能够发送和接收数据的应用程序,可以运行完整的Iceoryx系统。

!!! 注意
RouDi和所有应用程序必须使用相同的编译器和编译器标志进行构建。

首先我们需要启动RouDi。

# 如果已安装并在PATH环境变量中可用
iox-roudi
# 如果从头构建,使用tools中的脚本
$ICEORYX_ROOT/build/iox-roudi

之后,我们可以启动应用程序,它们会立即通过其运行时连接到RouDi。

当应用程序终止时,运行时会清理所有与RouDi通信所需的资源。这包括所有用于数据传输的内存块,这些内存块可能仍由应用程序持有。

在下一节详细介绍之前,以下动画描述了事件的过程。

Overview

现在我们简要定义一个Iceoryx系统中的主要实体,上述示例中部分实体已经使用。

1.2 RouDi

RouDi是Routing和Discovery的缩写。RouDi负责通信设置,但不实际参与发布者和订阅者或客户端和服务器之间的通信。RouDi可以看作是Iceoryx的总机操作员。它的另一个主要任务是设置共享内存,应用程序用它来交换负载数据。有时被称为守护进程,RouDi管理共享内存并负责服务发现,即使订阅者/客户端能够找到发布者/服务器提供的主题。它还跟踪所有已初始化运行时的应用程序,从而能够创建发布者、订阅者、服务器或客户端。它为应用程序提供查询这些信息的功能。

当一个应用程序崩溃时,RouDi会清理所有资源。由于我们大部分是无锁的进程间机制(仅剩一个锁,我们正在努力去除它),基于Iceoryx的通信比使用锁定的传统机制更可靠。

要查看RouDi的可用命令行选项,请调用$ICEORYX_ROOT/build/iox-roudi --help

1.3 共享内存

为了实现零拷贝的进程间通信,Iceoryx使用共享内存方法,即发布者和订阅者或客户端和服务器可以通过共享内存进行通信,从而实现零拷贝通信。

共享内存是物理内存,通过映射到其虚拟地址空间中的内存区域,使多个进程可访问。

有关更多信息,请参阅我们的共享内存概念文章

1.4 运行时

每个想要使用Iceoryx的应用程序都必须实例化其运行时,基本上是启用与RouDi的通信。

为此,需要以下代码行:

iox::runtime::PoshRuntime::initRuntime("some_unique_application_name");

运行时是用户应用程序中的一个对象,将共享内存映射到用户应用程序的地址空间。

!!! 注意
每个用户应用程序只允许一个运行时对象。

1.5 为主题创建服务描述

Iceoryx中的ServiceDescription表示一个主题,通过它发布者和订阅者或客户端和服务器可以交换数据,并由三个字符串标识符唯一标识。

  1. Group 名称
  2. Instance 名称
  3. Topic 名称

由这些字符串组成的三元组称为ServiceDescription。两个ServiceDescription在元素逐一相等时被认为是匹配的,即组、实例和主题名称相同。这意味着组和实例标识符可以忽略,以创建不同的ServiceDescription。它们将在将来的高级过滤功能中使用。

Iceoryx的服务模型源自AUTOSAR,并在API中使用这些名称(ServiceInstanceEvent)。所谓的规范协议在命名空间capro中实现。

下表概述了不同术语及其当前映射:

GroupInstanceTopic
rmw_iceoryxTypeNamespace/Topic-
AUTOSARServiceInstanceEvent
DDS Gateway--/Group/Instance/Topic
Cyclone DDS-Type NameTopic Name

服务与实例的关系类似于C++中的类与对象的关系。服务描述一个抽象主题,实例是该抽象的一个实例,类似于对象是一个实例化的类。事件在这种情况下类似于类的成员。

示例:

class MyRadarService {
   public:
      bool hasObstacleDetected;
      float distanceToObstacle;
};

MyRadarService frontLeftRadarInstance;
std::cout << frontLeftRadarInstance.hasObstacleDetected << std::endl;

在Iceoryx的世界中,我们可以订阅服务("MyRadarService", "frontLeftRadarInstance", "hasObstacleDetected"),每当检测到障碍物时就会接收样本。或者我们可以订阅distanceToObstacle,并接收一个持续的数据流,表示到障碍物的距离。

限制

传输数据的数据类型可以是任何C++类、结构或简单数据类型,只要它满足以下条件:

  • 不使用堆
  • 数据结构完全

包含在共享内存中 - 不指向进程本地内存的指针,不引用进程本地构造,不使用动态分配器

  • 数据结构必须是可重定位的,因此不能内部使用指针/引用
  • 没有虚成员函数
  • 不依赖析构函数被调用

!!! 注意
样本可能由没有共享内存写访问权限的进程释放。因此,在释放样本时不会调用析构函数。所有数据类型必须是可平凡析构的,或者至少不能依赖析构函数被调用。后者是Iceoryx容器(如cxx::vector)的情况,其中仅内部类型必须是可平凡析构的。

!!! 信息
大多数STL类型不能使用,但一些类型已重新实现以满足上述条件。您可以在这里找到概述。

1.6 发布者

发布者绑定到一个主题,需要一个服务描述来构建。如果它是类型化的,还需要指定数据类型作为模板参数。否则,发布者只知道原始内存,用户必须确保其正确解释。

一旦它提供了其主题,它就能够发布(发送)特定类型的数据。注意,默认情况下同一主题有多个发布者(n:m通信)。有一个编译时选项可以将Iceoryx限制为1:n通信。如果使用1:n通信,RouDi会检查同一主题上的多个发布者,并在一个主题有多个发布者时引发错误。

1.7 订阅者

订阅者也对应于一个主题,因此需要一个服务描述来构建。对于发布者,我们区分类型化和非类型化订阅者。

一旦订阅者订阅了某个主题,它就能够接收与该主题相关的类型数据。在非类型化的情况下,这是原始内存,用户必须确保它以与实际发送的数据兼容的方式进行解释。

当多个发布者提供同一主题时,订阅者将接收所有发布者的数据(但不同发布者之间的顺序是不确定的)。注意,订阅者不会从服务器或客户端接收数据,即使它们使用相同的主题。

1.8 客户端

类似于发布者和订阅者,客户端绑定到一个主题,需要一个服务描述来构建。如果客户端是类型化的,还需要指定请求和响应数据类型作为模板参数。在非类型化的情况下,客户端只知道原始内存,用户必须确保其正确解释。

一旦客户端连接到服务器,它就可以发送请求并接收服务器的响应。序列ID用于将响应与特定请求匹配。用户必须在请求时设置并在响应时检查。

1.9 服务器

与客户端类似,服务器需要一个服务描述来构建,可以是类型化或非类型化的。在类型化的情况下,用户必须提供请求和响应数据类型作为模板参数。否则,服务器处理原始内存,用户必须确保其正确解释。

一旦连接,服务器可以接收来自客户端的请求并发送相应的响应。

2 避免轮询

最简单的接收数据方法是定期轮询是否有数据可用,如下图左侧所示的发布-订阅消息模式。这对于简单的用例是足够的,但总体上效率不高,因为它通常会导致不必要的延迟和无数据唤醒。接收数据的另一种方法是等待用户定义的事件发生。我们的WaitSetListener提供了这种方法,以下部分将介绍它们。
请添加图片描述

2.1 WaitSet

WaitSet可用于通过非忙等待将线程置于睡眠状态,并等待用户定义的事件发生。通常,这些事件对应于特定订阅者或客户端的数据可用性。这样我们可以在数据可用时立即唤醒,并避免在没有数据可用时不必要的唤醒。

一个典型的用例是创建一个WaitSet,附加多个订阅者和/或客户端和用户触发器,然后等待一个或多个附加对象发出事件。如果发生这种情况,则会接收到一个名为notificationVector的所有发生事件的列表。这使得在订阅者或客户端通知WaitSet有新数据或新响应可用时,可以直接从订阅者或客户端收集数据。

WaitSet使用反应器模式,并通过推送策略通知用户发生了一个附加事件。

有关如何使用WaitSet的更多信息,请参阅我们的WaitSet示例

2.2 Listener

Listener可用于将自定义回调连接到用户定义的事件。与WaitSet不同,它通过在后台线程中执行连接的自定义回调来响应这些事件,该线程将由Listener创建。与WaitSet一样,后台线程在接收新数据时非忙等待。

!!! 注意
Listener是完全线程安全的,但请注意,大多数可以附加到Listener的对象不是线程安全的!这意味着要么对象完全由Listener处理,这应该是最常见的用例,要么用户必须通过其他手段确保线程安全,如将对象封装在线程安全的类中。

一个用例可能是创建一个Listener并附加多个订阅者。每当有新数据可用时,将执行相应的连接回调,例如打印到控制台或计算算法。另一个用例可能是将服务器附加到Listener,每当接收到请求时,执行创建并发送响应的连接回调。

与WaitSet一样,Listener使用反应器模式。

有关Listener的更多信息,请参阅我们的回调示例

3 API

API提供了两种语言版本,C++和C。详细信息请参阅C++示例C示例

C++ API的许多部分遵循函数式编程方法,这种方法不易出错。这需要使用单子类型iox::expectediox::optional,这些类型在这里介绍。

在C++ API中,我们区分typed APIuntyped API。在类型化API中,底层数据类型通过类型化指针或对某个数据类型T的引用(通常是模板参数)显现出来。这允许以C++惯用且类型安全的方式处理数据,应尽可能优先使用。类型化API主要用于独立使用Iceoryx时,即不集成到第三方框架中。

非类型化API提供不透明(即void)指针到数据,这种方式灵活且高效,但也要求用户确保正确解释接收到的数据,即与实际发送的数据类型兼容。这对于与其他低级API的交互和集成到第三方框架(如ROS)是必要的。有关更多信息,请参阅相应的头文件。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值