fastdds安装可以参考官方文档:
3. Linux installation from sources — Fast DDS 3.1.0 documentation
从INSTALLATION MANUAL这一节可以看出来,fastdds支持的操作系统包括linux、windows、qnx、MAC OS。本文记录通过源码和cmake的方式来安装fastdds的过程,也就是3.1.4节的安装过程:
1安装步骤
使用源码和cmake安装fastdds之前,需要先安装一些依赖,可以参考requirements和dependencies两节来安装。
camke、g++、pip3、wget、git | 这是常用一些工具 |
asio | asynchronous io,异步io库。fastdds作为一个通信中间件,底层的通信就是基于asio实现的 |
tinyxml2 | fastdds的配置文件格式为xml,tinyxml2用来解析xml文件 |
openssl | 用于加密 |
1、在家目录下创建一个文件夹
2、安装foonathan memory和fast cdr
foonathan memory提供内存管理功能,fast cdr提供序列化和反序列化。
CMAKE_INSTALL_PREFIX指定安装目录的前缀,如下的install指令,把xxx安装在lib目录下,实际的安装目录会在lib前加上前缀,以上边的命令为例,xxx实际会被安装到~/Fast-DDS/install/lib目录下。
install(TARGETS xxx DESTINATION lib)
3、安装fastdds
如果我们安装fastdds是为了学习或者调试,那么可以编译debug版本,也就是CMAKE_BUILD_TYPE=Debug;需要编译examples,则需要将examples打开,也就是COMPILE_EXAMPLES=ON。
cmake .. -DCMAKE_INSTALL_PREFIX=~/Fast-DDS/install -DCOMPILE_EXAMPLES=ON -DCMAKE_BUILD_TYPE=Debug
2helloworld
2.1fastdds应用的组成
helloworld是fastdds一个最简单的应用,一个fasttdds的应用往往包括以下几个部分:
topic:话题
topic也表示一个通道,表示reader和writer之间通信的通道,类似于我们的手机号、微信号、抖音号。topic需要在一个domain内保持唯一。topic在实际使用中用一个字符串来表示。helloword中使用的topic是hello_world_topic。
idl:interface definition language
helloworld的idl定义如下,可以看到,很像我们使用c/c++编程的时候使用的结构体。在具体使用的时候,idl不能直接用,要先使用fastddsgen工具将idl生成.h文件和.cpp。
idl定义了通信的数据内容,是writer和reader之间的通信的内容。
fastddsgen是用java开发的工具,用于将idl文件生成编程语言可以使用的格式。fastddsgen的安装说明如下:
3. Linux installation from sources — Fast DDS 3.1.0 documentation
如下是使用fastddsgen将HelloWorld.idl生成了.hpp,.cxx等文件
Publisher和Subscriber:
Publisher发布数据,Subscriber订阅数据。
xml
xml是fastdds应用使用的配置文件。xml中可以配置使用哪些传输层、qos配置、domain id配置、白名单配置等。如果想要使用配置文件,那么需要设置环境变量FASTDDS_DEFAULT_PROFILES_FILE。
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com" ><participant profile_name="hello_world_participant_profile" is_default_profile="true"><domainId>0</domainId><rtps><name>hello_world_participant</name></rtps></participant><data_writer profile_name="hello_world_datawriter_profile" is_default_profile="true"><qos><durability><kind>TRANSIENT_LOCAL</kind></durability><reliability><kind>RELIABLE</kind></reliability></qos><topic><historyQos><kind>KEEP_LAST</kind><depth>100</depth></historyQos><resourceLimitsQos><max_samples>100</max_samples><max_instances>1</max_instances><max_samples_per_instance>100</max_samples_per_instance></resourceLimitsQos></topic></data_writer><data_reader profile_name="hello_world_datareader_profile" is_default_profile="true"><qos><durability><kind>TRANSIENT_LOCAL</kind></durability><reliability><kind>RELIABLE</kind></reliability></qos><topic><historyQos><kind>KEEP_LAST</kind><depth>100</depth></historyQos><resourceLimitsQos><max_samples>100</max_samples><max_instances>1</max_instances><max_samples_per_instance>100</max_samples_per_instance></resourceLimitsQos></topic></data_reader>
</profiles>
topic定义了通信的通道,类似于手机号
idl定义了数据内容,类似于打电话的双方之间通话的内容
Publisher和Subsciber是通信的双方
2.2ListenerSubscriber和WaitsetSubscriber
数据的订阅侧有两种实现方式:
ListenerSubscriber | 1、使用简单,继承fastdds提供的基类 DataReaderListener,实现其中的接口on_subscription_matched、on_data_available,当有publisher和这个subscriber匹配的时候,on_subscription_matched会被调用;当有新的订阅数据到来的时候,on_data_available会被调用。 2、不够灵活,函数on_subscription_matched、on_data_available在哪个线程中调用,线程的调度参数是怎么配置的,一个Subscriber使用一个线程,还是多个Subscriber使用同一个线程,我们不能控制。 |
WaitsetSubsriber | 1、不需要继承fastdds提供的基类,使用灵活,subscriber运行在哪个线程中,线程的调度参数怎么配置,我们可以灵活的进行配置。 2、不如ListenerSubscriber使用简单 |
如下是helloworld中使用waitset的代码,waitset的使用分为如下3步:
①attach_condition,指定waitset关心哪些条件
②wait,等待条件到来
③使用一个for循环来遍历条件,在for循环中根据条件的类型进行对应的处理
看到这里,如果我们用过epoll的话,可以看到waitset的使用方式和epoll的使用方式是类似的,epoll的使用也可以分为如下3步:
①epoll_ctl,添加关心的事件
②epoll_wait,等待事件到来
③for循环处理事件,根据事件类型做对应处理
wait_set_.attach_condition(reader_->get_statuscondition());
wait_set_.attach_condition(terminate_condition_);void WaitsetSubscriberApp::run()
{while (!is_stopped()){ReturnCode_t ret_code = wait_set_.wait(triggered_conditions, eprosima::fastdds::dds::c_TimeInfinite);if (RETCODE_OK != ret_code){EPROSIMA_LOG_ERROR(SUBSCRIBER_WAITSET, "Error waiting for conditions");continue;}for (Condition* cond : triggered_conditions){StatusCondition* status_cond = dynamic_cast<StatusCondition*>(cond);if (nullptr != status_cond){Entity* entity = status_cond->get_entity();StatusMask changed_statuses = entity->get_status_changes();if (changed_statuses.is_active(StatusMask::subscription_matched())){...}if (changed_statuses.is_active(StatusMask::data_available())){... }}}}
}
2.3Publisher/Subscriber创建步骤
从helloworld的代码来看,创建Publisher和Subscriber的步骤是类似的:
①创建participant
②创建publisher或subscriber
③创建topic
④创建writer/reader
domain
域,domain是fastdds中一个范围最大的概念,一个domain用一个domain id来区分,只有同一个domain内的参与者才可以通信,不同参与者之间的对象不能通信。这给业务隔离带来了方便,比如我们实际使用中,业务A和业务B之间不需要通信,那么就可以将业务A和业务B分别部署在两个domain内。
participant
参与者,一个domain内可以有多个参与者,一个参与者只能属于一个domain。
publisher/subscriber
一个participant可以创建多个publisher/subscriber。
writer/reader
一个publisher可以创建多个writer,一个subscriber可以创建多个reader。
idl定义的数据类型,也就是通信的内容,和topic是绑定的,在创建topic的时候,需要指定type,这里的type就是idl中定义的数据类型。
topic_ = participant_->create_topic(topic_name, type_.get_type_name(), topic_qos);
topic和writer/reader是绑定的,在创建writer/reader的时候需要指定topic:
writer_ = publisher_->create_datawriter(topic_, writer_qos, this, StatusMask::all());
qos,也就是服务质量,在创建publisher/subscriber、topic、writer/reader时均需要配置qos。
2.4运行helloworld
在两个终端分别启动subscriber和publisher,运行结果如下两张图所示。可以看出,当puhlisher和subscriber匹配的时候,会打印日志;当其中一方退出的时候,另一方也会打印unmatched日志。如果subscriber侧要使用waitset方式,可以执行./hello_world subscriber -w。