- 阅读方式(全程参考官网)
1. 从代码依赖切入,理解整体流程
2. 从业务点维度切入,配合官网、调试代码,如QOS、域
3. 从commit记录入手
4. 从架构设计切入,查阅相关资料(递进关系)
- 按层划分
- DDS层
eProsima Fast DDS exposes two different APIs to interact with the communication service at different levels. The main API is the Data Distribution Service (DDS) Data-Centric Publish-Subscribe (DCPS) Platform Independent Model (PIM) API, or DDS DCPS PIM for short, which is defined by the Data Distribution Service (DDS) version 1.4 specification, to which Fast DDS complies. This section is devoted to explain the main characteristics and modes-of-use of this API under Fast DDS, providing an in depth explanation of the five modules into which it is divided:
Core: It defines the abstract classes and interfaces that are refined by the other modules. It also provides the Quality of Service (QoS) definitions, as well as support for the notification-based interaction style with the middleware.
Domain: It contains the DomainParticipant class that acts as an entry-point of the Service, as well as a factory for many of the classes. The DomainParticipant also acts as a container for the other objects that make up the Service.
Publisher: It describes the classes used on the publication side, including Publisher and DataWriter classes, as well as the PublisherListener and DataWriterListener interfaces.
Subscriber: It describes the classes used on the subscription side, including Subscriber and DataReader classes, as well as the SubscriberListener and DataReaderListener interfaces.
Topic: It describes the classes used to define communication topics and data types, including Topic and TopicDescription classes, as well as TypeSupport, and the TopicListener interface.
- RTPS层
eprosima Fast DDS的较低级别RTPS层用于实现RTPS标准中定义的协议。该层比DDS层对通信协议的内部提供了更多的控制,因此高级用户可以更好地控制库的功能。
- Discovery
Fast DDS作为数据分发服务(DDS)的实现,提供了一种发现机制,允许在DomainParties之间自动查找和匹配DataWriter和DataReader,以便他们可以开始共享数据。对于所有机制,这一发现分两个阶段进行。
5.1. 发现阶段
参与者发现阶段(PDP):在此阶段,域参与者承认彼此的存在。为此,每个DomainParticipant都会定期发送公告消息,其中指定了DomainParticitant监听传入元数据和用户数据流量的单播地址(IP和端口)。当两个给定的域参与者存在于同一DDS域中时,它们将匹配。默认情况下,公告消息使用众所周知的多播地址和端口(使用DomainId计算)发送。此外,可以指定一个地址列表,使用单播发送公告(请参阅初始对等体)。此外,还可以配置此类公告的周期性(参见发现配置)。
端点发现阶段(EDP):在此阶段,数据写入器和数据读取器相互确认。为此,DomainParticites使用PDP期间建立的通信信道相互共享有关其DataWriter和DataReader的信息。除其他内容外,此信息还包含主题和数据类型(请参阅主题)。为了使两个端点匹配,它们的主题和数据类型必须一致。一旦DataWriter和DataReader匹配,它们就可以发送/接收用户数据流量。
重要事项
可以使用PDP阶段来传输有关DomainParticipant运行的主机、用户和进程(物理信息)的信息。有关如何配置传输的物理数据的更多信息,请参阅发现信息中的物理数据。
5.2. 发现机制
Fast DDS提供以下发现机制:
简单发现:这是默认机制。它支持PDP和EDP的RTPS标准,因此与任何其他DDS和RTPS实现兼容。
静态发现:此机制在PDP阶段使用简单参与者发现协议(SPDP)(如RTPS标准所规定),但允许在预先知道所有数据写入器和数据读取器的IP和端口、数据类型和主题时跳过简单端点发现协议(SEDP)阶段。
发现服务器:此发现机制使用集中式发现架构,其中DomainParticipant(称为服务器)充当元流量发现的中心。
手动发现:此机制仅与RTPS层兼容。它禁用PDP,允许用户使用其选择的任何外部元信息通道手动匹配和取消匹配RTPSP参与者、RTPSReaders和RTPSWriters。因此,用户必须访问DomainParticipant实现的RTPSP参与者,并直接匹配RTPS实体。
发现机制是DDS中的一个关键组成部分,它允许发布者和订阅者自动找到彼此,而无需手动配置它们之间的通信路径。下面是每个DiscoveryProtocol选项的详细解释:
NONE:
在这个模式下,不使用任何发现机制。发布者和订阅者之间不会自动链接,即使它们有相同的主题名。所有匹配和通信路径的建立都必须通过手动调用addReaderLocator、addReaderProxy、addWriterProxy等方法来完成。这种模式适用于静态配置或测试环境。
SIMPLE:
使用简单的发现机制,遵循DDS互操作性线协议规范(RTPS, Real-time Publish-Subscribe Protocol DDS Interoperability Wire Protocol Specification)。这种机制允许发布者和订阅者通过RTPS协议自动发现和建立连接,而无需手动配置。
EXTERNAL:
在这个模式下,用户需要提供一个自定义的参与者发现协议(PDP, Participant Discovery Protocol)子类对象来处理发现逻辑。框架不会管理这个对象的生命周期,用户需要自己负责创建、使用和销毁它。这种模式提供了最大的灵活性,允许用户根据特定需求定制发现过程。
CLIENT:
参与者将作为客户端参与发现操作。在这种模式下,用户需要指定服务器定位器(Server Locators)作为属性,以便客户端能够找到并连接到服务器。客户端通常不会主动广播其存在,而是等待来自服务器的发现信息。
SERVER:
参与者将作为服务器参与发现操作。服务器会主动广播其存在,以便客户端能够找到并连接到它。与BACKUP模式不同,这种模式下的发现操作是易失的,即如果系统关闭并重新启动,发现握手过程将需要重新进行。
BACKUP:
与SERVER模式类似,参与者也将作为服务器参与发现操作。但不同之处在于,在这种模式下,发现信息会被持久化到文件中。这意味着如果系统关闭并重新启动,发现信息可以从文件中恢复,无需重新进行发现握手过程。
SUPER_CLIENT:
参与者将作为超级客户端参与内部操作。在这种模式下,远程服务器会将该参与者视为服务器,并与之共享所有发现信息。这种模式允许超级客户端在发现过程中扮演双重角色,既可以从其他服务器接收发现信息,也可以像服务器一样广播其存在。
这些发现协议为DDS系统提供了不同的配置选项,以适应不同的应用场景和需求。
- PropertyPolicyQos Options
This section contains the list of PropertyPolicyQos that can be set with Fast DDS:
13.1. Non consolidated QoS
13.1.1. DataWriter operating mode QoS Policy
13.1.2. Unique network flows QoS Policy
13.1.3. Statistics Module Settings
13.1.4. Endpoint Partitions
13.1.5. Static Discovery’s Exchange Format
13.2. Flow Controller Settings
13.3. Persistence Service Settings
13.4. Security Plugins Settings
13.5. Logging Module Settings
- Environment variables
https://fast-dds.docs.eprosima.com/en/v2.6.5/fastdds/env_vars/env_vars.html
- Dynamic Topic Types
动态主题类型
eProsima Fast DDS提供了一种动态方式来定义和使用主题类型和主题数据。我们的实现遵循DDS接口的OMG可扩展和动态主题类型。有关更多信息,您可以阅读DDS XTypes V1.2的规范。
动态主题类型提供了在RTPS上工作的可能性,而不受与IDL相关的限制。使用它们,用户可以声明他们需要的不同类型并直接管理信息,避免了更新IDL文件和生成C++类的额外步骤。
- Logging
eProsima Fast DDS提供了一个可扩展的内置日志模块,该模块公开了以下主要功能:
三种不同的日志级别:Log::Kind::Info、Log::Kind::Warning和Log::Kind::Error(请参阅日志消息)。根据不同的条件进行过滤:类别、内容或源文件(请参阅过滤器)。输出到STDOUT、STDERR和/或日志文件(请参阅消费者)。本节旨在解释Fast DDS日志模块的使用、配置和可扩展性。
-
XML profiles
eProsima Fast DDS允许加载XML配置文件,每个文件包含一个或多个XML配置文件。除了加载用户XML文件的API函数外,Fast DDS还尝试在初始化时查找和加载几个XML文件。快速DDS提供以下选项:
加载位于当前执行路径中的名为DEFAULT_FASTRPS_PROFILES.XML的XML文件。加载一个XML文件,该文件的位置是使用环境变量FASTRTPS_DEFALT_PROFILES_file定义的(请参阅FASTRTPS_DEFAULT_PROFILES_file)。
1. 直接从类的定义中加载配置参数,而无需在工作目录中查找DEFAULT_FASTRPS_PROFILES.xml(请参阅SKIP_DEFALT_xml)。
2. 直接将XML作为字符串数据缓冲区加载。
XML配置文件由一个唯一的名称定义,该名称用于在创建实体、回收站配置或DynamicTypes定义期间引用XML配置文件。这两个选项都可以补充,即可以加载多个XML文件,但这些文件不能有同名的XML配置文件。本节介绍如何使用XML配置文件配置DDS实体。这包括对每个XML配置文件可用的所有配置值的描述,以及如何创建完整的XML文件。
- Typical Use-Cases
典型使用案例
快速DDS是高度可配置的,这允许它在大量场景中使用。本节提供了处理分布式系统时以下典型用例的配置示例:
1. WIFI上的快速DDS。介绍了一个通过多播通信进行发现是一个挑战的案例。此示例显示了如何使用远程参与者的地址端口对配置对等体的初始列表(请参阅配置初始对等体)。
2. 禁用组播发现机制(请参阅禁用组播发现)。
3. 配置SERVER发现机制(请参阅发现服务器)。
4. 众所周知的网络部署。描述预先知道整个实体网络拓扑(参与者、发布者、订阅者及其地址和端口)的情况。在这种环境中,Fast DDS允许完全避免配置STATIC发现机制的发现阶段。
5. 许多订阅者的主题。在有许多DataReader订阅同一主题的情况下,使用多播传递可以帮助减少网络和CPU的开销。
6. 大数据速率。提供配置选项,可以在发布服务器和订阅服务器之间交换的数据量较大的情况下提高性能,这可能是由于数据大小或消息速率造成的。这些示例描述了如何:
配置套接字缓冲区大小(请参阅增加缓冲区大小)。
限制发布速率(请参阅流量控制器)。
调整套接字缓冲区的大小(请参阅增加套接字缓冲区大小)。
调整心跳周期(请参阅调整心跳周期)。
配置非严格可靠性模式(请参阅使用非严格可靠性)。
7. 实时行为。描述允许在实时场景中使用Fast DDS的配置选项。示例描述了如何:
配置内存管理以避免动态内存分配(请参阅调整分配)。
限制API函数的阻塞时间,使其具有可预测的响应时间(请参阅非阻塞调用)。
8. 减少内存使用。对于内存消耗受限的用例,可以配置Fast DDS,通过调整不同的QoS策略将内存占用降至最低。
9. 零拷贝通信。在某些约束条件下,Fast DDS可以在发布和订阅节点之间提供应用级通信,避免在此过程中进行任何数据复制。
10. 独特的网络流。此用例说明了允许请求唯一网络流和识别正在使用的API。
11. 动态网络接口。如果应用程序运行时网络接口预计会发生变化,Fast DDS提供了一种简单的方法来重新扫描可用接口并将其包含在内。
12. 统计模块。此用例解释了如何在受监视的应用程序中启用统计模块,以及如何创建统计监视应用程序。
13. ROS 2使用快速DDS中间件。由于Fast DDS是每个OSRF机器人操作系统2(ROS 2)长期(LTS)版本和大多数非LTS版本中的默认中间件实现,因此本文档包括一个完整的独立部分,展示ROS 2中库的使用,以及如何在ROS 2项目中充分利用Fast DDS的一系列功能。
14. 如何使用eProsima DDS录制和回放(rosbag2和DDS)。关于如何调整应用程序以使用ROS 2 rosbag2包录制和重放DDS消息的说明。
- Transport Layer
传输层在DDS实体之间提供通信服务,负责通过物理传输实际发送和接收消息。DDS层将此服务用于用户数据和发现流量通信。然而,DDS层本身是独立于传输的,它定义了一个传输API,并且可以在任何实现这个API的传输插件上运行。这样,它就不局限于特定的传输,应用程序可以选择最适合其要求的传输,也可以创建自己的传输。
eProsima Fast DDS已经实现了五种传输方式:
UDPv4:IPv4上的UDP数据报通信。如果没有给出特定的传输配置,则默认情况下会在新的DomainParticipant上创建此传输(请参阅UDP传输)。
UDPv6:IPv6上的UDP数据报通信(请参阅UDP传输)。
TCPv4:IPv4上的TCP通信(请参阅TCP传输)。
TCPv6:IPv6上的TCP通信(参见TCP传输)。
SHM:在同一主机上运行的实体之间的共享内存通信。如果没有给出特定的传输配置,则默认情况下会在新的DomainParticipant上创建此传输(请参阅共享内存传输)。
虽然它不是传输模块的一部分,但在某些设置下,进程内数据传递和数据共享传递也可用于在实体之间发送消息。下图显示了Fast DDS中可用的不同传输之间的比较。
- ROS 2 using Fast DDS middleware
ROS 2技术栈和快速DDS之间的接口由ROS 2包rmw_fastrtps提供。该软件包在所有ROS 2发行版中都可用,包括二进制文件和源代码。rmw_fastrtps实际上提供的不是一个,而是两个不同的ROS 2中间件实现,它们都使用Fast DDS作为中间件层:rmw_fastrts_cpp和rmw_fastRTS_dynamics_cpp。两者之间的主要区别在于,rmw_fastrtps_dynamic_cpp在运行时使用内省类型支持来决定序列化/反序列化机制,而rmw_fastrts_cpp使用自己的类型支持,在构建时为每种消息类型生成映射。在Foxy之前,默认的ROS 2 RMW实现是RMW_fastrtps_cpp。对于Galactic,必须将环境变量RMW_IMPLEMENTATION设置为选择RMW_fastrtps_cpp,以便将Fast DDS用作中间件层。
- Security
DDS安全规范包括五个安全内置插件。
身份验证插件:DDS:Auth:PKI-DH。此插件使用受信任的证书颁发机构(CA)为加入DDS域的每个DomainParticipant提供身份验证。支持DomainParties之间的相互身份验证,并建立共享密钥。
访问控制插件:DDS:访问:权限。此插件为执行受保护操作的DomainParties提供访问控制。
加密插件:DDS:加密:AES-GCM-GMAC。此插件在伽罗瓦计数器模式(AES-GCM)中使用高级加密标准(AES)提供经过身份验证的加密。
日志插件:DDS:日志:DDS_LogTopic。此插件记录安全事件。
数据标记:DDS:标记:DDS_Discovery。此插件允许向数据添加安全标签。因此,可以指定数据的分类级别。在DDS环境中,它可以用作访问控制的补充,基于数据标记创建访问控制;用于消息优先级排序;并且防止其被中间件使用而被应用程序或服务使用。
-
Statistics
快速DDS统计模块是快速DDS的扩展,可以重新收集与DDS通信有关的数据。收集的数据使用DDS在专用主题上发布,使用统计模块中的内置数据写入器。因此,默认情况下,Fast DDS不会编译此模块,因为它可能会影响应用程序的性能。尽管如此,在CMake配置步骤中,可以使用-DFASTDS_STATISTIS=ON激活Statistics模块。
- Persistentence Service
Fast-DDS提供了持久化服务(Persistence Service),允许将DataWriter的历史记录配置为存储在持久数据库(如文件系统、数据库等)中。这样,即使DataWriter被销毁或应用程序重启,历史记录也可以被重新加载。要启用持久化服务,通常需要在配置文件中设置相应的参数,或在代码中调用相应的API来初始化持久化服务,并指定DataWriter的持久化策略(如存储位置、存储格式等)。
代码如下:
bool TestWriterPersistent::init()
{
//CREATE PARTICIPANT
RTPSParticipantAttributes PParam;
PParam.builtin.discovery_config.discoveryProtocol = eprosima::fastrtps::rtps::DiscoveryProtocol::SIMPLE;
PParam.builtin.use_WriterLivelinessProtocol = true;
mp_participant = RTPSDomain::createParticipant(0, PParam);
if (mp_participant == nullptr)
{
return false;
}
//CREATE WRITERHISTORY
// 配置持久化属性
HistoryAttributes hatt;
hatt.payloadMaxSize = 255;
hatt.maximumReservedCaches = 50;
mp_history = new WriterHistory(hatt);
PropertyPolicy property_policy;
property_policy.properties().emplace_back("dds.persistence.plugin", "builtin.SQLITE3");
property_policy.properties().emplace_back("dds.persistence.sqlite3.filename", "test.db");
//CREATE WRITER
WriterAttributes watt;
watt.endpoint.reliabilityKind = BEST_EFFORT;
watt.endpoint.durabilityKind = TRANSIENT;
watt.endpoint.persistence_guid.guidPrefix.value[11] = 1;
watt.endpoint.persistence_guid.entityId.value[3] = 1;
watt.endpoint.properties = property_policy;
std::cout << "PID: " << watt.endpoint.persistence_guid << std::endl;
mp_writer = RTPSDomain::createRTPSWriter(mp_participant, watt, mp_history, &m_listener);
if (mp_writer == nullptr)
{
return false;
}
return true;
}
---->>>
// Get persistence service
IPersistenceService* persistence = nullptr;
if (!get_persistence_service(is_builtin, param.endpoint, persistence))
{
return false;
}
--->>>
IPersistenceService* PersistenceFactory::create_persistence_service(
const PropertyPolicy& property_policy)
{
IPersistenceService* ret_val = nullptr;
const std::string* plugin_property = PropertyPolicyHelper::find_property(property_policy, "dds.persistence.plugin");
if (plugin_property != nullptr)
{
#if HAVE_SQLITE3
if (plugin_property->compare("builtin.SQLITE3") == 0)
{
const std::string* filename_property = PropertyPolicyHelper::find_property(property_policy,
"dds.persistence.sqlite3.filename");
#ifdef ANDROID
const char* filename = (filename_property == nullptr) ?
"/data/local/tmp/persistence.db" : filename_property->c_str();
#else
const char* filename = (filename_property == nullptr) ?
"persistence.db" : filename_property->c_str();
#endif // if ANDROID
bool update_schema = false;
const std::string* update_schema_value = PropertyPolicyHelper::find_property(property_policy,
"dds.persistence.update_schema");
if (update_schema_value != nullptr &&
((update_schema_value->compare("TRUE") == 0) ||
(update_schema_value->compare("true") == 0)))
{
update_schema = true;
}
ret_val = create_SQLite3_persistence_service(filename, update_schema);
}
#endif // if HAVE_SQLITE3
}
return ret_val;
}
--->>>
如上,如果配置了持久化属性,就会在createWriter时创建持久化服务。
--->>>
然后,根据is_reliable条件创建StatelessPersistentWriter/StatefulPersistentWriter。
bool RTPSParticipantImpl::createWriter(
RTPSWriter** WriterOut,
WriterAttributes& param,
WriterHistory* hist,
WriterListener* listen,
const EntityId_t& entityId,
bool isBuiltin)
{
auto callback = [hist, listen, this]
(const GUID_t& guid, WriterAttributes& param, fastdds::rtps::FlowController* flow_controller,
IPersistenceService* persistence, bool is_reliable) -> RTPSWriter*
{
if (is_reliable)
{
if (persistence != nullptr)
{
return new StatefulPersistentWriter(this, guid, param, flow_controller,
hist, listen, persistence);
}
else
{
return new StatefulWriter(this, guid, param, flow_controller,
hist, listen);
}
}
else
{
if (persistence != nullptr)
{
return new StatelessPersistentWriter(this, guid, param, flow_controller,
hist, listen, persistence);
}
else
{
return new StatelessWriter(this, guid, param, flow_controller,
hist, listen);
}
}
};
return create_writer(WriterOut, param, entityId, isBuiltin, callback);
}
- 业务
- 流控
流控制器用于管理DDS(Data Distribution Service)通信中的数据流,确保数据能够按照特定的策略(如同步、异步等)进行发布和传输。DDS是一种基于发布/订阅模式的中间件,用于在分布式系统中高效地分发数据。
测试代码
// Create slow DataWriter
DataWriterQos wsqos;
wsqos.publish_mode().kind = ASYNCHRONOUS_PUBLISH_MODE;
// This controller allows 300kb per second.
ThroughputControllerDescriptor slowPublisherThroughputController{300000, 1000};
wsqos.throughput_controller() = slowPublisherThroughputController;
slow_writer_ = slow_publisher_->create_datawriter(topic_, wsqos, &m_listener);
--->>>
在创建dataWriter里面触发流控逻辑
GUID_t guid(m_guid.guidPrefix, entId);
fastdds::rtps::FlowController* flow_controller = nullptr;
const char* flow_controller_name = param.flow_controller_name;
// Support of old flow controller style.
if (param.throughputController.bytesPerPeriod != UINT32_MAX && param.throughputController.periodMillisecs != 0)
{
flow_controller_name = guid_str_.c_str();
if (ASYNCHRONOUS_WRITER == param.mode)
{
fastdds::rtps::FlowControllerDescriptor old_descriptor;
old_descriptor.name = guid_str_.c_str();
old_descriptor.max_bytes_per_period = param.throughputController.bytesPerPeriod;
old_descriptor.period_ms = param.throughputController.periodMillisecs;
flow_controller_factory_.register_flow_controller(old_descriptor);
flow_controller = flow_controller_factory_.retrieve_flow_controller(guid_str_.c_str(), param);
}
else
{
EPROSIMA_LOG_WARNING(RTPS_PARTICIPANT,
"Throughput flow controller was configured while writer's publish mode is configured as synchronous." \
"Throughput flow controller configuration is not taken into account.");
}
}
if (m_att.throughputController.bytesPerPeriod != UINT32_MAX && m_att.throughputController.periodMillisecs != 0)
{
if (ASYNCHRONOUS_WRITER == param.mode && nullptr == flow_controller)
{
flow_controller_name = guid_str_.c_str();
flow_controller = flow_controller_factory_.retrieve_flow_controller(guid_str_, param);
}
else
{
EPROSIMA_LOG_WARNING(RTPS_PARTICIPANT,
"Throughput flow controller was configured while writer's publish mode is configured as synchronous." \
"Throughput flow controller configuration is not taken into account.");
}
}
--->>>
初始化流控线程
/*!
* Initialize asynchronous thread.
*/
template<typename PubMode = PublishMode>
typename std::enable_if<!std::is_same<FlowControllerPureSyncPublishMode, PubMode>::value, void>::type
initialize_async_thread()
{
bool expected = false;
if (async_mode.running.compare_exchange_strong(expected, true))
{
// Code for initializing the asynchronous thread.
async_mode.thread = create_thread([this]()
{
run();
}, thread_settings_, "dds.asyn.%u.%u", participant_id_, async_index_);
}
}
--->>>
流控线程内容
/*!
* Function run by the asynchronous thread.
*/
void run()
{
while (async_mode.running)
{
// There are writers interested in removing a sample.
if (0 != async_mode.writers_interested_in_remove)
{
continue;
}
std::unique_lock<fastrtps::TimedMutex> lock(mutex_);
fastrtps::rtps::CacheChange_t* change_to_process = nullptr;
//Check if we have to sleep.
{
std::unique_lock<fastrtps::TimedMutex> in_lock(async_mode.changes_interested_mutex);
// Add interested changes into the queue.
sched.add_interested_changes_to_queue_nts();
while (async_mode.running &&
(async_mode.force_wait() || nullptr == (change_to_process = sched.get_next_change_nts())))
{
// Release main mutex to allow registering/unregistering writers while this thread is waiting.
lock.unlock();
bool ret = async_mode.wait(in_lock);
in_lock.unlock();
lock.lock();
in_lock.lock();
if (ret)
{
sched.trigger_bandwidth_limit_reset();
}
sched.add_interested_changes_to_queue_nts();
}
}
fastrtps::rtps::RTPSWriter* current_writer = nullptr;
while (nullptr != change_to_process)
{
// Fast check if next change will enter.
if (!async_mode.fast_check_is_there_slot_for_change(change_to_process))
{
break;
}
if (nullptr == current_writer || current_writer->getGuid() != change_to_process->writerGUID)
{
auto writer_it = writers_.find(change_to_process->writerGUID);
assert(writers_.end() != writer_it);
current_writer = writer_it->second;
}
if (!current_writer->getMutex().try_lock())
{
break;
}
fastrtps::rtps::LocatorSelectorSender& locator_selector =
current_writer->get_async_locator_selector();
async_mode.group.sender(current_writer, &locator_selector);
locator_selector.lock();
// Remove previously from queue, because deliver_sample_nts could call FlowController::remove_sample()
// provoking a deadlock.
fastrtps::rtps::CacheChange_t* previous = change_to_process->writer_info.previous;
fastrtps::rtps::CacheChange_t* next = change_to_process->writer_info.next;
previous->writer_info.next = next;
next->writer_info.previous = previous;
change_to_process->writer_info.previous = nullptr;
change_to_process->writer_info.next = nullptr;
change_to_process->writer_info.is_linked.store(false);
fastrtps::rtps::DeliveryRetCode ret_delivery = current_writer->deliver_sample_nts(
change_to_process, async_mode.group, locator_selector,
std::chrono::steady_clock::now() + std::chrono::hours(24));
if (fastrtps::rtps::DeliveryRetCode::DELIVERED != ret_delivery)
{
// If delivery fails, put the change again in the queue.
change_to_process->writer_info.is_linked.store(true);
previous->writer_info.next = change_to_process;
next->writer_info.previous = change_to_process;
change_to_process->writer_info.previous = previous;
change_to_process->writer_info.next = next;
async_mode.process_deliver_retcode(ret_delivery);
locator_selector.unlock();
current_writer->getMutex().unlock();
// Unlock mutex_ and try again.
break;
}
locator_selector.unlock();
current_writer->getMutex().unlock();
sched.work_done();
if (0 != async_mode.writers_interested_in_remove)
{
// There are writers that want to remove samples.
break;
}
// Add interested changes into the queue.
{
std::unique_lock<fastrtps::TimedMutex> in_lock(async_mode.changes_interested_mutex);
sched.add_interested_changes_to_queue_nts();
}
change_to_process = sched.get_next_change_nts();
}
async_mode.group.sender(nullptr, nullptr);
}
}
- 过滤
- datashared---还是通过共享内存实现共享
// CREATE THE WRITER
DataWriterQos wqos = DATAWRITER_QOS_DEFAULT;
wqos.reliability().kind = BEST_EFFORT_RELIABILITY_QOS;
wqos.history().depth = 10;
// 这行代码配置了数据共享(data sharing)策略。data_sharing().automatic()调用启用了自动数据共享功能,这允许DDS系统优化数据的分发,特别是在多个DataWriter写入相同数据,或者多个DataReader订阅相同数据的场景下。通过自动数据共享,DDS可以减少网络带宽的使用,因为相同的数据样本只需要在网络中传输一次,然后可以被多个DataReader共享。
wqos.data_sharing().automatic();
writer_ = publisher_->create_datawriter(topic_, wqos, &listener_);
--->>>
RTPSWriter侧逻辑(创建域的时候进行检查)
if (att.endpoint.data_sharing_configuration().kind() != OFF)
{
std::shared_ptr<WriterPool> pool = std::dynamic_pointer_cast<WriterPool>(payload_pool);
if (!pool || !pool->init_shared_memory(this, att.endpoint.data_sharing_configuration().shm_directory()))
{
logError(RTPS_WRITER, "Could not initialize DataSharing writer pool");
}
}
persistenceWriter侧逻辑(创建域的时候进行检查)
// Prepare the changes for datasharing if compatible
if (att.endpoint.data_sharing_configuration().kind() != OFF)
{
auto pool = std::dynamic_pointer_cast<WriterPool>(payload_pool);
assert(pool != nullptr);
for (auto change : hist->m_changes)
{
pool->add_to_shared_history(change);
}
}
RTPSReader侧逻辑(创建域的时候开线程检测)
if (att.endpoint.data_sharing_configuration().kind() != OFF)
{
using std::placeholders::_1;
std::shared_ptr<DataSharingNotification> notification =
DataSharingNotification::create_notification(
getGuid(), att.endpoint.data_sharing_configuration().shm_directory());
if (notification)
{
is_datasharing_compatible_ = true;
datasharing_listener_.reset(new DataSharingListener(
notification,
att.endpoint.data_sharing_configuration().shm_directory(),
att.matched_writers_allocation,
this));
// We can start the listener here, as no writer can be matched already,
// so no notification will occur until the non-virtual instance is constructed.
// But we need to stop the listener in the non-virtual instance destructor.
datasharing_listener_->start();
}
}
- QOS
- 域
一、DDS域的定义
DDS域是一个用于链接所有能够相互通信的应用程序的概念。在DDS中,域代表了一个单独的通信平面,它在共享公共通信基础设施的实体之间创建了逻辑分离。从概念上讲,DDS域可以看作是一个虚拟网络,连接在同一域上运行的所有应用程序,并将它们与运行在不同域上的应用程序隔离开来。
二、DDS域的特点
唯一性:每个DDS域都有一个唯一的标识符,称为domainId。共享此domainId的应用程序属于同一个域并能够相互通信。
独立性:不同的DDS域彼此完全独立,域之间没有数据共享。这意味着一个域中的应用程序无法直接访问或订阅另一个域中发布的数据。
灵活性:DDS允许在同一物理网络上同时存在多个DDS域,从而允许您在同一物理网络上拥有多个虚拟分布式系统。这种灵活性对于需要隔离不同应用程序或进行独立测试的场景非常有用。
三、DDS域的作用
隔离:DDS域提供了一种机制来隔离不同的应用程序或系统组件,确保它们之间的通信不会相互干扰。
组织:通过定义不同的DDS域,可以方便地组织和管理分布式系统中的各个组件,使系统结构更加清晰和易于维护。
安全:由于DDS域之间的独立性,它可以作为实现安全策略的一种手段。通过限制不同域之间的通信,可以防止未经授权的访问和数据泄露。
四、DDS域与DDS通信的关系
在DDS通信中,DomainParticipant是域中应用程序的本地成员身份,它充当了其他DCPS(Data-Centric Publish-Subscribe,以数据为中心的发布订阅)实体的容器和工厂。DomainParticipant定义了DomainId以指定它所属的DDS域,并允许应用程序在域内创建和配置发布者(Publisher)、订阅者(Subscriber)、主题(Topic)、数据写入者(DataWriter)和数据读取者(DataReader)等实体。这些实体通过DDS中间件进行通信和数据交换,实现分布式系统的功能。
- watchdog
- 读代码过程中的名词 Or 疑问记录
执行器&回调组
从设计角度来看,ROS 2中的执行器和回调组之间存在着紧密的逻辑关系。执行器负责管理和调度回调函数,而回调组则决定了回调函数的执行方式。通过合理配置执行器和回调组,可以实现高效的异步处理机制,满足ROS 2系统对实时性和性能的要求。
业务层如何创建域参与者
构造创建,我们封装后直接定义对象即可,原生的源码需要继承rclcpp::Node即可
如何把writer跟reader匹配
如何配置
域隔离
封装细节
主要是把rclcpp::Node原有必须侵入式(继承)依赖的封装成了一个共享指针单例节点,以定义节点的形式使用。使得业务解耦于中间件。
架构设计
代码为什么难读
- 工程上问题&&解决