🥁作者:
华丞臧
📕专栏:
【C++】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(
点赞+收藏+关注
)。如果有错误的地方,欢迎在评论区指出。推荐一款刷题网站 👉
LeetCode
前言
本文章主要介绍三个任务,一是在服务器上安装一个自己的ubuntu虚拟机;二是在ubuntu虚拟机上编译开源项目FastDDS;三是编译FastDDS的ShapesDemo。本文安装虚拟机、编译FastDDS以及ShapesDemo使用的都是ubuntu虚拟机,镜像为ubuntu-20.04.6-live-server-amd64.iso。
一、虚拟机的安装
1.1 安装ubuntu虚拟机
通过查阅资料,我了解到服务器上安装虚拟机可以使用kvm软件,使用下面的指令可以安装kvm软件及其依赖项:
yum -y install qemu-kvm libvirt libvirt-python libguestfs-tools virt-install virt-clone virt-v2v virt-manager virt-viewer
创建虚拟机,首先需要一个磁盘镜像文件,这里使用qemu-img来创建,指令如下:
qemu-img create -f qcow2 vm01.qcow2 20G # 新建磁盘镜像文件
有了磁盘镜像文件就可以开始创建虚拟机了,我采用命令行式创建虚拟机,使用ubuntu-20.04.6-live-server-amd64.iso镜像,指令如下:
virt-install --virt-type=kvm --name=ub01 --vcpus=16 --memory=16384
--location /home/iso/ubuntu-20.04.6-live-server-amd64.iso,
kernel=casper/vmlinuz,initrd=casper/initrd
--disk path=/home/imags/zch/vm01.qcow2,size=80,format=qcow2
--network bridge=br0 --graphics none --extra-args='console=ttyS0' --force
虚拟机创建好之后会进入安装界面,设置好用户和密码就可以开始使用ubuntu虚拟机了。
1.2 网络配置
创建好并进入虚拟机后,网络是不可用的,我们可以通过配置
/etc/netplan/00-installer-config.yaml
文件使网络可用,配置文件内容如下:
# This is the network config written by 'subiquity'
network:
ethernets:
ens2:
addresses: [192.168.2.225/24] # 与宿主机在同一网段即可
gateway4: 192.168.2.1
dhcp4: false
optional: true
nameservers:
addresses: [192.168.2.1, 8.8.8.8]
version: 2
renderer: networkd
文件配置好后,输入
sudo netplan apply
指令使配置生效,即可使用网络。
1.3 kvm软件部分指令
virsh list --all # 显示所有已经定义的虚拟机的状态信息
virsh start ub01 # 启动名为ub01虚拟机
virsh console ub01 # 连接名为ub01虚拟机
virsh shutdown ub01 # 关闭名为ub01虚拟机
virsh destroy ub01 # 摧毁名为ub01虚拟机
virsh undefine ub01 # 删除名为ub01虚拟机
二、FastDDS开源项目
2.1 FastDDS介绍
2.1.1 什么是DDS
数据分发服务 (DDS)
是一种以数据为中心的通信协议,用于分布式软件应用程序通信。它描述了支持数据提供程序和数据使用者之间通信的通信应用程序编程接口 (API) 和通信语义。由于它是以数据为中心的发布订阅 (DCPS) 模型,因此在其实现中定义了三个关键应用程序实体:发布实体,用于定义信息生成对象及其属性;订阅实体,定义信息消耗对象及其属性;以及定义作为主题传输的信息类型的配置实体,并使用其服务质量 (QoS) 属性创建发布者和订阅者,以确保上述实体的正确性能。
简单来说DDS就是一种以数据为中心的通信协议,用于分布式软件应用程序通信。而FastDDS一个基于DDS标准实现的开源库,是FastRTPS库的升级版本,为DDS标准提供高性能和可扩展性的实现。
2.1.2 DCPS模型
在DCPS模型中主要有四个基本元素:
-
发布者(Publisher)
:负责创建和配置其实现的
DataWriters
的 DCPS 实体。
DataWriters
是负责实际发布消息的实体。每个主题都有一个分配的
Topic
,用于发布消息。 -
订阅者(Subscriber)
:DCPS实体负责接收在其订阅的主题下发布的数据。它提供一个或多个
DataReader
对象,这些对象负责将新数据的可用性传达给应用程序。 -
话题(Topic)
:它是绑定发布和订阅的实体。它在 DDS 域中是唯一的。通过
TopicDescription
,它允许发布和订阅的数据类型的统一性。 -
域(Domain)
:这是用于链接属于一个或多个应用程序的所有发布者和订阅者的概念,这些发布者和订阅者在不同主题下交换数据。这些参与域的单个应用程序称为
DomainParticipant
。
DDS 域由域 ID 标识。域参与者定义域 ID 以指定其所属的 DDS 域。具有不同 ID 的两个域参与者不知道彼此在网络中的存在。因此,可以创建多个通信渠道。这适用于涉及多个 DDS 应用程序,其各自的域参与者相互通信的场景,但这些应用程序不得干扰。
DomainParticipant
充当其他 DCPS 实体的容器,充当
Publisher
、
Subscriber和Topic
实体的工厂,并在域中提供管理服务。
2.1.3 RTPS协议
实时发布订阅(RTPS)协议是为支持DDS而开发的,是一种发布订阅同行中间件,通信功能强大,支持UDP/IP、TCP以及共享内存(SHM)。
RTPS中定义了域的概念,它定义了一个单独的通信平面。多个域可以同时并存,域中可以包含任意数量的RTPSParticipants,即能够发送接收数据的元素。RTPSParticipants有两种端点:
- RTPSWriter:发送数据
- RTPSReader:接收数据
RTPSParticipant中可以有任意数量的发送和接收的端点。他们通过围绕Topic展开,Topic定义和标记正在交换的数据,参与者通过RTPSWriter将数据写入Topic,也可以通过RTPSReader来接收与订阅相关的数据。
2.2 FastDDS的安装及编译
2.2.1 FastDDS安装
FastDDS
官方给出了三种方式安装,我使用从源代码安装FastDDS库到Linux的方式。从源代码在 Linux 环境中安装
eProsima Fast DDS
,将安装以下软件包:
-
foonathan_memory_vendor
,一个与 STL 兼容的C++内存分配器
库
。 -
fastcdr
,一个根据
标准 CDR
序列化机制进行序列化的C++库。 -
fastrtps
,
eProsima Fast DDS
库的核心库。
2.2.2 工具需求
安装过程中所需要的工具如下:
- CMake, g++, pip3, wget 和 git
使用下面的指令安装所需工具:
sudo apt install cmake g++ python3-pip wget git
2.2.3 依赖项
eProsima Fast DDS
在从 Linux 环境中的源代码安装时必须具有以下依赖项:
-
sudo apt install libasio-dev libtinyxml2-dev
-
sudo apt install libssl-dev
-
sudo apt install libp11-dev libengine-pkcs11-openssl sudo apt install softhsm2 # 请注意,softhsm2 软件包会创建一个名为 softhsm 的新组。要授予对 HSM 模块的访问权限,用户必须属于此组 sudo usermod -a -G softhsm 用户名
具体安装方法参考
eProsima Fast DDS
。
2.2.4 本地安装
eProsima Fast DDS
创建一个目录用来下载和构建
eProsima Fast DDS
及其依赖项:
mkdir Fast-DDS
克隆以下依赖项并用cmake编译:
-
安装fastrtps
# 安装colcon和vcstool pip3 install -U colcon-common-extensions vcstool # 下载安装fastrtps cd ~/Fast-DDS wget https://raw.githubusercontent.com/eProsima/Fast-DDS/master/fastrtps.repos mkdir src vcs import src < fastrtps.repos # 如果vcs找不到可以运行下面的指令 ~/.local/bin/vcs import src < fastrtps.repos # 构建包 colcon build # 如果colcon找不到 ~/.local/bin/colcon build # 本地准备环境 source ~/Fast-DDS/install/setup.bash # 或者 echo 'source ~/Fast-DDS/install/setup.bash' >> ~/.bashrc
-
安装foonathan_memory_vendor
cd ~/Fast-DDS git clone https://github.com/eProsima/foonathan_memory_vendor.git mkdir foonathan_memory_vendor/build cd foonathan_memory_vendor/build cmake .. -DCMAKE_INSTALL_PREFIX=~/Fast-DDS/install -DBUILD_SHARED_LIBS=ON cmake --build . --target install
-
安装Fast-CDR
cd ~/Fast-DDS git clone https://github.com/eProsima/Fast-CDR.git mkdir Fast-CDR/build cd Fast-CDR/build cmake .. -DCMAKE_INSTALL_PREFIX=~/Fast-DDS/install cmake --build . --target install
安装好上述依赖项就可以安装
eProsima Fast-DDS
:
cd ~/Fast-DDS
git clone https://github.com/eProsima/Fast-DDS.git
mkdir Fast-DDS/build
cd Fast-DDS/build
cmake .. -DCMAKE_INSTALL_PREFIX=~/Fast-DDS/install
cmake --build . --target install
2.2.5 样例测试
在~/FastDDS中创建一个文件夹demo用来跑测试样例:
cd ~/Fast-DDS
mkdir demo && cd demo
# 也可以不用克隆,在安装fastdds中可以找到DynamicHelloWorldExample,路径为Fast-DDS/examples/cpp/dds
git clone https://github.com/eProsima/FastDDS/tree/master/examples/cpp/dds/DynamicHelloWorldExample
mkdir build && cd build
cmake ..
CMakeLists.txt文件解读
# 说明cmake最低版本要求
cmake_minimum_required(VERSION 3.16.3)
# 定义项目名称为DDSHelloWorldExample,指定版本号和编程语言C++
project(DDSHelloWorldExample VERSION 1 LANGUAGES CXX)
# 查找依赖项
if(NOT fastcdr_FOUND)
find_package(fastcdr REQUIRED) # 如果没找到使用find_package命令来查找
endif()
if(NOT fastrtps_FOUND)
find_package(fastrtps REQUIRED)
endif()
# 检查编译器是否支持C++11标准
include(CheckCXXCompilerFlag)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
check_cxx_compiler_flag(-std=c++11 SUPPORTS_CXX11)
if(NOT SUPPORTS_CXX11)
message(FATAL_ERROR "Compiler doesn't support C++11")
endif()
endif()
message(STATUS "Configuring HelloWorld example...") # 输出信息
file(GLOB DDS_HELLOWORLD_EXAMPLE_SOURCES_CXX "*.cxx") # 定义一个变量表示"*.cxx"
file(GLOB DDS_HELLOWORLD_EXAMPLE_SOURCES_CPP "*.cpp") # 定义一个变量表示"*.cpp"
# 使用${}依赖项生成DDSHelloWorldExample可执行文件
add_executable(DDSHelloWorldExample ${DDS_HELLOWORLD_EXAMPLE_SOURCES_CXX} ${DDS_HELLOWORLD_EXAMPLE_SOURCES_CPP})
# 为可执行文件添加编译定义选项
target_compile_definitions(DDSHelloWorldExample PRIVATE
$<$<AND:$<NOT:$<BOOL:${WIN32}>>,$<STREQUAL:"${CMAKE_BUILD_TYPE}","Debug">>:__DEBUG>
$<$<BOOL:${INTERNAL_DEBUG}>:__INTERNALDEBUG> # Internal debug activated.
)
# 可执行文件链接库
target_link_libraries(DDSHelloWorldExample fastrtps fastcdr fastdds::optionparser)
# 将DDSHelloWorldExample安装到指定路径
install(TARGETS DDSHelloWorldExample
RUNTIME DESTINATION examples/cpp/dds/HelloWorldExample/${BIN_INSTALL_DIR})
编译完成HelloWorldExample后,在build文件夹中找到DDSHelloWordExample可执行程序,在虚拟机上运行两个程序,如下:
./DDSHelloWordExample publisher
./DDSHelloWordExample subscriber
我们可以在不同主机(服务器)上安装同样的样例程序来测试,也可以在同一台服务器上不同的虚拟机上测试。下图是在同一服务器的不同虚拟机上进行测试的结果:
三、ShapesDemo安装及编译
3.1 什么是ShapesDemo?
ShapesDemo
是一个常见的示例程序,用于演示分布式系统中使用DDS(Data Distribution Service)实现数据发布和订阅的过程。
该示例程序通常以图形形状(如圆、矩形、三角形等)为数据对象,展示了如何在发布者和订阅者之间进行实时数据的传输和交互。
在
ShapesDemo
示例中,通常会有两个主要组件:
-
发布者
(Publisher):发布者负责创建并发送图形形状的数据,将其发布到DDS中。它可以定义不同类型的图形形状对象,并定期更新这些对象的状态。发布者还负责在DDS网络中注册和广播所发布的数据。 -
订阅者
(Subscriber):订阅者订阅感兴趣的图形形状数据,并接收来自发布者的更新信息。它可以订阅特定类型的图形形状或所有类型的数据。订阅者从DDS中接收到发布者发送的数据,并对其进行处理、显示或执行其他操作。
通过运行
ShapesDemo
示例程序,我们能够观察到发布者创建图形形状对象并发送数据,而订阅者接收并处理这些数据的过程。这有助于理解DDS在实时数据传输中的工作原理以及如何使用DDS实现分布式系统中的数据发布和订阅功能。
3.2 ShapesDemo安装
ShapesDemo即形状演示,可以理解为用图形演示。Fast-DDS的ShapesDemo就是用图形来演示Fast-DDS,让我们能够以更加形象直观的理解FastDDS。
3.2.1 从源码安装到ubuntu
安装ShapesDemo前,用户必须确保ubuntu上Qt5,可以在终端上运行一下指令来安装:
sudo apt install -y qtbase5-dev
可以使用colcon安装ShapesDemo,首先安装colcon和vcs,安装指令如下:
pip install -U colcon-common-extensions vcstool
创建一个ShapesDemo文件夹用来下载安装eProsima Shapes Demo 及其依赖项的存储库文件,步骤如下:
mkdir -p ShapesDemo/src && cd ShapesDemo
wget https://raw.githubusercontent.com/eProsima/ShapesDemo/master/shapes-demo.repos
vcs import src < shapes-demo.repos
# 找不到vcs,尝试使用下面的指令
~/.local/bin/vcs import src < shapes-demo.repos
# 构建包
colcon build
# 找不到colcon,尝试使用下面的指令
~/.local/bin/colcon build
链接应用程序可执行文件以使其可从当前目录访问:
source install/setup.bash
运行ShapesDemo:
ShapesDemo
3.2.2 形状演示
使用服务器上的虚拟机,用xshell连接的服务器,而xshell并不支持弹窗,ShapesDemo运行会弹出一个窗口用来做形状演示。我这里使用的是Xmanager软件来支持ShapesDemo,使用Xmanager配置步骤如下:
-
创建一个XDMCP会话,配置如下,其中主机为虚拟机ip地址。
-
配置DISPLAY环境变量
export DISPLAY=192.168.3.240:0.0 #此ip为使用的电脑ip # 注意如果使用同一台主机上的两个虚拟机来演示,其中一个DISPLAY配置如下 export DISPLAY=192.168.3.240:1.0
ShapesDemo运行结果如下:
四、扩展
4.1 Fast-DDS-Gen
Fast-DDS-Gen
是使用Gradle构建的,Gradle是一个开源的构建自动化工具,需要执行Java版本。
FasT-DDS-Gen
是一个代码生成工具,用于根据DDS标准规范生成相关的代码。它使用IDL描述数据类型和通信接口,并根据IDL文件生成相应的数据类型定义、消息传输代码和序列化/反序列化代码等。
4.1.1 依赖项
-
安装Java JDK
sudo apt install openjdk-11-jdk
4.1.2 编译Fast-DDS-Gen
cd ~
git clone --recursive https://github.com/eProsima/Fast-DDS-Gen.git
cd Fast-DDS-Gen
./gradlew assemble
4.1.3 测试
首先我们要编写一个IDL文件,内容如下:
struct Test
{
string message;
unsigned long index;
};
编写并保存好IDL文件,接下来就可以使用Fast-DDS-Gen工具来生成代码了,运行下面的指令:
<path-to-Fast-DDS-Gen>/scripts/fastddsgen -example CMake Test.idl
代码生成完我们可以来测试这些代码,运行下面的指令:
mkdir build && cd build
cmake ..
make
结果如下图:
4.2 XML配置文件
使用fastddsgen和idl文件可以自动帮我们生成主体代码。但当需要使用一个新的类型时,我们每次都需要新建一个idl文件并使用fastddsgen工具来生成代码。使用XML配置文件,我们就可以直接在XML文件中添加我们所需要的类型即可进行使用,不用fastddsgen工具来生成代码。
XML配置文件是一种常见的用于配置应用程序或系统的文件格式。它使用XML(可扩展标记语言)的语法,以层次结构的方式组织和描述配置信息
。XML文件中类型配置文件的定义是标签式的,每个元素可以包含一个或多个类型定义。在一个元素中定义多个类型或为每个元素定义单个类型具有相同的结果。
-
XML可用基本类型的标识符
boolean
char8
char16
byte
octet
uint8
int8
int16
int32
uint16
uint32
int64
uint64
float32
float64
float128
string
wstring
-
XML配置文件实例:
<?xml version="1.0" encoding="UTF-8" ?> # XML声明,指定XML版本和字符编码 <types xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles" > #使用网址标识的命名空间 <type> # 类型元素 <struct name="HelloWorld"> # 创捷结构体,名为HelloWorld <member name="message" type="string"/> # 结构体成员变量 <member name="index" type="uint32"/> <member name="array" type="uint32" arrayDimensions="5,2"/> # 5*2的数组 </struct> # 结构体结束标志 </type> # 类型结束标志 <type> <struct name="Test"> <member name="message" type="string"/> <member name="index" type="uint32"/> </struct> </type> </types>
使用fastddsgen和IDL与使用XML配置文件相比,前者会用IDL结构体生成一个类,同时声明定义其数据类型封装结构体;后者不需要封装目标结构体并声明定义,
eProsima Fast DDS
中提供了一些接口用来获取目标结构以及其成员变量、定义对象等,下面代码是一段实例:
// 加载目标xml文件,并判断是否加载成功
if (eprosima::fastrtps::xmlparser::XMLP_ret::XML_OK != eprosima::fastrtps::xmlparser::XMLProfileManager::loadXMLFile("helloworld_example_type_profile.xml"))
{ // 加载失败
std::cout <<
"Cannot open XML file \"helloworld_example_type_profile.xml\". Please, run the publisher from the folder "
<< "that contatins this XML file." << std::endl;
return false;
}
// 获取动态数据类型Test,并用build构建该类型对象
eprosima::fastrtps::types::DynamicType_ptr dyn_type =
eprosima::fastrtps::xmlparser::XMLProfileManager::getDynamicTypeByName("Test")->build();
// 使用动态类型创建TypeSupport
TypeSupport m_type(new eprosima::fastrtps::types::DynamicPubSubType(dyn_type));
// 创建动态数据对象
m_Hello = eprosima::fastrtps::types::DynamicDataFactory::get_instance()->create_data(dyn_type);
//m_Hello->set_string_value("hello", 0); // 两个参数:value id(id与XML文件中定义成员的顺序相关)
m_Hello->set_uint32_value(0, 1);
使用XML配置文件,我们就只需要完成发布者、订阅者、主函数代码的编写即可。具体实例代码请看
XMLFastDDSExample
-
当我在三个虚拟机上测试代码时,结果如下:
4.3 Dynamic
eProsima Fast DDS
还提供了一种动态定义的方式,这种方式让我们可以不用依赖代码文件以外的文件来定义所需要的结构。eProsima Fast 提供了一些接口在代码中来定义结构体数据类型,我们可以用这些接口定义自己所需要的数据类型。
下面是一段代码实例:
void DynamicDataPublisher::publishData()
{
static long long i = 0;
std::cout << "第" << std::to_string(i) << "次更新数据" << std::endl;
// 根据动态类型构建对象
auto struct_data =
DynamicDataFactory::get_instance()->create_data(dynamic_struct_ptr_);
assert(struct_data != nullptr);
// 设置结构体的值
assert(struct_data->set_byte_value(i % 127, 0) == ReturnCode_t::RETCODE_OK);
assert(struct_data->set_int32_value(i % (0xffffffff / 2 - 1), 1) ==
ReturnCode_t::RETCODE_OK);
assert(
struct_data->set_string_value(std::string("seq:") + std::to_string(i),
2) == ReturnCode_t::RETCODE_OK);
std::cout << "数据为:" << i % 127 << " " << i % (0xffffffff / 2 - 1)
<< " " << std::string("seq:") + std::to_string(i) << " "
<< std::endl;
data_writer_->write(struct_data);
++i;
}
void DynamicDataPublisher::create_dynamic_struct_type()
{
// 创建byte类型构建器
DynamicTypeBuilder_ptr base_type_builder =
DynamicTypeBuilderFactory::get_instance()->create_byte_builder();
assert(base_type_builder != nullptr);
// 创建byte(位集)动态对象
auto base_type = base_type_builder->build();
// 创建int32构建器
DynamicTypeBuilder_ptr base_type_builder2 =
DynamicTypeBuilderFactory::get_instance()->create_int32_builder();
assert(base_type_builder2 != nullptr);
auto base_type2 = base_type_builder2->build(); // 创建int32动态对象
// 创建string构建器
DynamicTypeBuilder_ptr base_type_builder3 =
DynamicTypeBuilderFactory::get_instance()->create_string_builder();
assert(base_type_builder3 != nullptr);
auto base_type3 = base_type_builder3->build();
// 创建结构体构建器
DynamicTypeBuilder_ptr struct_type_builder =
DynamicTypeBuilderFactory::get_instance()->create_struct_builder();
assert(struct_type_builder != nullptr);
// 构建名为MyStruct的结构体
struct_type_builder->set_name("MyStruct");
// 增加结构体成员
assert(struct_type_builder->add_member(0, "byte_value", base_type) ==
ReturnCode_t::RETCODE_OK);
assert(struct_type_builder->add_member(1, "int32_value", base_type2) ==
ReturnCode_t::RETCODE_OK);
assert(struct_type_builder->add_member(2, "string_value", base_type3) ==
ReturnCode_t::RETCODE_OK);
// 创建MyStruct动态类型对象
auto struct_type = struct_type_builder->build();
dynamic_struct_ptr_ = struct_type;
// 创建类型支持对象
eprosima::fastdds::dds::TypeSupport typeSupport(
new eprosima::fastrtps::types::DynamicPubSubType(struct_type));
typeSupport.get()->auto_fill_type_information(false);//指定是否自动填充类型信息
typeSupport.get()->auto_fill_type_object(true);// 指定是否自动填充类型对象
typeSupport.register_type(this->participant_);//注册类型
}
动态类型具体使用方法可以参考
eprosima Fast DDS
。