DDS(Data Distribution Service)
是一套规范,包含许多组件,其中,DCPS(Data-Centric Publish-Subscribe)
是DDS的核心层,它定义了数据如何在发布者和订阅者之间交换。RTPS (Real-Time Publish-Subscribe)
是DDS规范的网络层协议
FastDDS
是DDS的一种实现,实现有两种模型,一种DDS模型,一种RTPS模型,RTPS能够更加精细化地操作底层。
也就是说,当使用 DDS 开发应用程序时,实际上是在利用 DCPS 模型,并且这个模型通常会通过 RTPS 协议来实现数据的传输。
本文档描述了使用FastDDS进行测试,对不同数据大小的数据进行测试,搭建了一套测试框架,并给出结论。
!!!本文不对FastDDS的安装进行说明。
1. 测试概述
本文从简单的发布订阅协议的FastDDS测试样例开始,到最终搭建出一套简单的可定制测试的发布订阅框架,方便进行测试。
1.1. 测试环境及指标
环境:
Win10主机下的VMWare虚拟机环境
OS:Ubuntu22.04LTS
CPU:4x2=8(核)
内存:8G RAM
磁盘:100G
FastDDS版本:2.14.2
软件需求:
CMake
FastDDS库
gcc
指标:
CPU占用、内存占用
1.2. 测试阶段
测试分为4个阶段
简单的发布订阅->能够配置简单Qos->通过xml文件配置->搭建测试框架->完整测试框架+测试脚本
因为每一节基本都包含了大量代码,所以最好跳着看。
若读者对FastDDS的传输已经有了一定的理解,可以直接跳到1.2.5.节进行查看。
1.2.1.简单发布订阅
1.2.2.配置历史记录
1.2.3.xml文件配置
1.2.4.测试框架
1.2.5.完整测试框架
1.2.1.简单发布订阅
1)目录结构
.
├── CMakeLists.txt
├── Readme.md(非必要)
├── restart.sh
└── src
├── HelloWorld.idl
├── HelloWorldPublisher.cpp
└── HelloWorldSubscriber.cpp
2)测试需要执行
chmod +x ./restart.sh
# 在src下根据idl文件生成所需文件
fastddsgen src/HelloWorld.idl
# 编译文件
./restart.sh
# 本窗口
./build/DDSHelloWorldPublisher
# 另外一个窗口
./build/DDSHelloWorldSubscriber
3)文件内容
CMakeLists.txt内容如下
cmake_minimum_required(VERSION 3.20)
project(DDSHelloWorld)
if(NOT fastrtps_FOUND)
find_package(fastrtps REQUIRED)
endif()
if(NOT fastcdr_FOUND)
find_package(fastcdr REQUIRED)
endif()
# Set C++11
include(CheckCXXCompilerFlag)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG OR
CMAKE_CXX_COMPILER_ID MATCHES "Clang")
check_cxx_compiler_flag(-std=c++11 SUPPORTS_CXX11)
if(SUPPORTS_CXX11)
add_compile_options(-std=c++11)
else()
message(FATAL_ERROR "Compiler doesn't support C++11")
endif()
endif()
message(STATUS "Configuring HelloWorld publisher/subscriber example...")
# 注意 将src/*.cxx添加到DDS_HELLOWORLD_SOURCES_CXX变量中
file(GLOB DDS_HELLOWORLD_SOURCES_CXX "src/*.cxx")
add_executable(DDSHelloWorldPublisher src/HelloWorldPublisher.cpp ${DDS_HELLOWORLD_SOURCES_CXX})
target_link_libraries(DDSHelloWorldPublisher fastrtps fastcdr)
add_executable(DDSHelloWorldSubscriber src/HelloWorldSubscriber.cpp ${DDS_HELLOWORLD_SOURCES_CXX})
target_link_libraries(DDSHelloWorldSubscriber fastrtps fastcdr)
restart.sh内容如下:
# /usr/bin
rm -rf ./build &&
mkdir build &&
cd build &&
cmake .. &&
cmake --build .
HelloWorld.idl
struct HelloWorld
{
unsigned long index;
string message;
};
HelloWorldPublisher.cpp
/**
* @file HelloWorldPublisher.cpp
*
*/
#include "HelloWorldPubSubTypes.h"
#include <chrono>
#include <thread>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/DataWriterListener.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
using namespace eprosima::fastdds::dds;
class HelloWorldPublisher
{
private:
HelloWorld hello_;
DomainParticipant* participant_;
Publisher* publisher_;
Topic* topic_;
DataWriter* writer_;
TypeSupport type_;
class PubListener : public DataWriterListener
{
public:
PubListener()
: matched_(0)
{
}
~PubListener() override
{
}
void on_publication_matched(
DataWriter*,
const PublicationMatchedStatus& info) override
{
if (info.current_count_change == 1)
{
matched_ = info.total_count;
std::cout << "Publisher matched." << std::endl;
}
else if (info.current_count_change == -1)
{
matched_ = info.total_count;
std::cout << "Publisher unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for PublicationMatchedStatus current count change." << std::endl;
}
}
std::atomic_int matched_;
} listener_;
public:
HelloWorldPublisher()
: participant_(nullptr)
, publisher_(nullptr)
, topic_(nullptr)
, writer_(nullptr)
, type_(new HelloWorldPubSubType())
{
}
virtual ~HelloWorldPublisher()
{
if (writer_ != nullptr)
{
publisher_->delete_datawriter(writer_);
}
if (publisher_ != nullptr)
{
participant_->delete_publisher(publisher_);
}
if (topic_ != nullptr)
{
participant_->delete_topic(topic_);
}
DomainParticipantFactory::get_instance()->delete_participant(participant_);
}
//!Initialize the publisher
bool init()
{
hello_.index(0);
hello_.message("Firstfastddsdemo1");
DomainParticipantQos participantQos;
participantQos.name("Participant_publisher");
participant_ = DomainParticipantFactory::get_instance()->create_participant(0, participantQos);
if (participant_ == nullptr)
{
return false;
}
// Register the Type
type_.register_type(participant_);
// Create the publications Topic
topic_ = participant_->create_topic("HelloWorldTopic", "HelloWorld", TOPIC_QOS_DEFAULT);
if (topic_ == nullptr)
{
return false;
}
// Create the Publisher
publisher_ = participant_->create_publisher(PUBLISHER_QOS_DEFAULT, nullptr);
if (publisher_ == nullptr)
{
return false;
}
// Create the DataWriter
writer_ = publisher_->create_datawriter(topic_, DATAWRITER_QOS_DEFAULT, &listener_);
if (writer_ == nullptr)
{
return false;
}
return true;
}
//!Send a publication
bool publish()
{
if (listener_.matched_ > 0)
{
hello_.index(hello_.index() + 1);
writer_->write(&hello_);
return true;
}
return false;
}
//!Run the Publisher
void run(
uint32_t samples)
{
uint32_t samples_sent = 0;
while (samples_sent < samples)
{
if (publish())
{
samples_sent++;
std::cout << "Message: " << hello_.message() << " with index: " << hello_.index()
<< " SENT" << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
};
int main(
int argc,
char** argv)
{
std::cout << "Starting publisher." << std::endl;
uint32_t samples = 10;
HelloWorldPublisher* mypub = new HelloWorldPublisher();
if(mypub->init())
{
mypub->run(samples);
}
delete mypub;
return 0;
}
HelloWorldSubscriber.cpp
/**
* @file HelloWorldSubscriber.cpp
*
*/
#include "HelloWorldPubSubTypes.h"
#include <chrono>
#include <thread>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
using namespace eprosima::fastdds::dds;
class HelloWorldSubscriber
{
private:
DomainParticipant* participant_;
Subscriber* subscriber_;
DataReader* reader_;
Topic* topic_;
TypeSupport type_;
class SubListener : public DataReaderListener
{
public:
SubListener()
: samples_(0)
{
}
~SubListener() override
{
}
void on_subscription_matched(
DataReader*,
const SubscriptionMatchedStatus& info) override
{
if (info.current_count_change == 1)
{
std::cout << "Subscriber matched." << std::endl;
}
else if (info.current_count_change == -1)
{
std::cout << "Subscriber unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
}
}
void on_data_available(
DataReader* reader) override
{
SampleInfo info;
if (reader->take_next_sample(&hello_, &info) == ReturnCode_t::RETCODE_OK)
{
if (info.valid_data)
{
std::cout <<"Only recieved: "<<hello_.index()<<" "<<hello_.message()<<std::endl;
samples_++;
std::cout << "Message: " << hello_.message() << " with index: " << hello_.index()
<< " RECEIVED." << std::endl;
}
}
}
HelloWorld hello_;
std::atomic_int samples_;
}
listener_;
public:
HelloWorldSubscriber()
: participant_(nullptr)
, subscriber_(nullptr)
, topic_(nullptr)
, reader_(nullptr)
, type_(new HelloWorldPubSubType())
{
}
virtual ~HelloWorldSubscriber()
{
if (reader_ != nullptr)
{
subscriber_->delete_datareader(reader_);
}
if (topic_ != nullptr)
{
participant_->delete_topic(topic_);
}
if (subscriber_ != nullptr)
{
participant_->delete_subscriber(subscriber_);
}
DomainParticipantFactory::get_instance()->delete_participant(participant_);
}
//!Initialize the subscriber
bool init()
{
DomainParticipantQos participantQos;
participantQos.name("Participant_subscriber");
participant_ = DomainParticipantFactory::get_instance()->create_participant(0, participantQos);
if (participant_ == nullptr)
{
return false;
}
// Register the Type
type_.register_type(participant_);
// Create the subscriptions Topic
topic_ = participant_->create_topic("HelloWorldTopic", "HelloWorld", TOPIC_QOS_DEFAULT);
if (topic_ == nullptr)
{
return false;
}
// Create the Subscriber
subscriber_ = participant_->create_subscriber(SUBSCRIBER_QOS_DEFAULT, nullptr);
if (subscriber_ == nullptr)
{
return false;
}
// Create the DataReader
reader_ = subscriber_->create_datareader(topic_, DATAREADER_QOS_DEFAULT, &listener_);
if (reader_ == nullptr)
{
return false;
}
return true;
}
//!Run the Subscriber
void run(
uint32_t samples)
{
while (listener_.samples_ < samples)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
};
int main(
int argc,
char** argv)
{
std::cout << "Starting subscriber." << std::endl;
uint32_t samples = 10;
HelloWorldSubscriber* mysub = new HelloWorldSubscriber();
if (mysub->init())
{
mysub->run(samples);
}
delete mysub;
return 0;
}
4)运行结果
1.2.2.配置历史记录
通过对简单发布订阅代码的理解,摘出其中必要的部分,并对其进行Qos的扩展,再进行测试
1)目录结构
.
├── CMakeLists.txt
├── pub.sh
├── sub.sh
├── Readme.md(非必要)
├── restart.sh
└── src
├── Example.idl
├── HelloWorldPublisher.cpp
└── HelloWorldSubscriber.cpp
2)测试时执行
chmod +x ./restart.sh
chmod +x ./pub.sh
chmod +x ./sub.sh
# 在src下根据idl文件生成所需文件
fastddsgen src/Example.idl
# 编译文件
./restart.sh
# 本窗口
./pub.sh
# 另外一个窗口
./sub.sh
3)文件内容
CMakeLists.txt和restart.sh与简单发布订阅一致,所以不再列出。
Example.idl
module Example {
struct Message {
unsigned long id;
string<256> text;
};
};
pub.sh
# /usr/bin
cd build
./DDSHelloWorldPublisher
sub.sh
# /usr/bin
cd build
./DDSHelloWorldSubscriber
HelloWorldPublisher.cpp
#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/DataWriterListener.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>
using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyPubListener : public DataWriterListener
{
public:
MyPubListener()
: matched_(0)
{
}
~MyPubListener() override
{
}
void on_publication_matched(
DataWriter*,
const PublicationMatchedStatus& info) override
{
if (info.current_count_change == 1)
{
matched_ = info.total_count;
std::cout << "Publisher matched." << std::endl;
}
else if (info.current_count_change == -1)
{
matched_ = info.total_count;
std::cout << "Publisher unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for PublicationMatchedStatus current count change." << std::endl;
}
}
std::atomic_int matched_;
};
int main()
{
DomainParticipantQos dq;
// 初始化DDS域参与者
DomainParticipant* participant = DomainParticipantFactory::get_instance()->create_participant(0, dq);
// 注册类型
TypeSupport type(new Example::MessagePubSubType());
type.register_type(participant);
// 创建发布者
Publisher* publisher = participant->create_publisher(PUBLISHER_QOS_DEFAULT, nullptr);
// 创建主题
TopicQos topic_qos = TOPIC_QOS_DEFAULT;
topic_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
Topic* topic = participant->create_topic("MessageTopic", type.get_type_name(), topic_qos);
std::cout<<type.get_type_name()<<std::endl;
// 创建数据写入者
MyPubListener Mypl;
DataWriterQos writer_qos = DATAWRITER_QOS_DEFAULT;
writer_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
writer_qos.reliability().kind = RELIABLE_RELIABILITY_QOS;
writer_qos.reliability().max_blocking_time = {5, 0};
writer_qos.history().depth = 5;
DataWriter* writer = publisher->create_datawriter(topic, writer_qos,&Mypl);
// 发布数据
Example::Message msg;
msg.text("Hello");
for (long i = 1; i <= 10; ++i) {
while(Mypl.matched_==0)
{
std::cout<<"wait"<<std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
msg.id(i);
writer->write(&msg);
std::cout << "Published message with id: " << i << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// 清理资源
publisher->delete_datawriter(writer);
participant->delete_publisher(publisher);
participant->delete_topic(topic);
DomainParticipantFactory::get_instance()->delete_participant(participant);
return 0;
}
HelloWorldSubscriber.cpp
#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>
using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyReaderListener : public DataReaderListener
{
public:
void on_subscription_matched(
DataReader* reader,
const SubscriptionMatchedStatus& info) override
{
if (info.current_count_change == 1)
{
std::cout << "Subscriber matched." << std::endl;
}
else if (info.current_count_change == -1)
{
std::cout << "Subscriber unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
}
}
void on_data_available(DataReader* reader) override {
std::cout<<"data_available"<<std::endl;
Example::Message msg;
SampleInfo info;
if (reader->take_next_sample(&msg, &info) == ReturnCode_t::RETCODE_OK) {
if (info.valid_data) {
std::cout << "Received message with id: " << msg.id() <<"text:"<<msg.text()<< std::endl;
}
}
}
};
int main()
{
// 设置TCP传输
DomainParticipantQos dq;
// 初始化DDS域参与者
DomainParticipant* participant = DomainParticipantFactory::get_instance()->create_participant(0, dq);
// 注册类型
TypeSupport type(new Example::MessagePubSubType());
type.register_type(participant);
// 创建订阅者
Subscriber* subscriber = participant->create_subscriber(SUBSCRIBER_QOS_DEFAULT);
// 创建主题
TopicQos topic_qos = participant->get_default_topic_qos();
topic_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
Topic* topic = participant->create_topic("MessageTopic", type.get_type_name(), topic_qos);
// 创建数据读取者
DataReaderQos reader_qos = DATAREADER_QOS_DEFAULT;
reader_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
reader_qos.reliability().kind = RELIABLE_RELIABILITY_QOS;
reader_qos.reliability().max_blocking_time = {5, 0};
reader_qos.history().depth = 5;
DataReader* reader = subscriber->create_datareader(topic, reader_qos, new MyReaderListener());
static int count = 0;
// 等待接收消息
while (count<=10) {
std::this_thread::sleep_for(std::chrono::seconds(1));
count++;
}
// 清理资源
subscriber->delete_datareader(reader);
participant->delete_subscriber(subscriber);
participant->delete_topic(topic);
DomainParticipantFactory::get_instance()->delete_participant(participant);
return 0;
}
4)运行结果
1.2.3.xml文件配置
通过xml进行配置,效果与配置历史记录相似
1)目录结构
.
├── CMakeLists.txt
├── pub.sh
├── sub.sh
├── Readme.md(非必要)
├── restart.sh
└── src
├── Example.idl
├── toal.xml
├── HelloWorldPublisher.cpp
└── HelloWorldSubscriber.cpp
2)测试时执行
chmod +x ./restart.sh
chmod +x ./pub.sh
chmod +x ./sub.sh
# 在src下根据idl文件生成所需文件
fastddsgen src/Example.idl
# 编译文件
./restart.sh
# 本窗口
./pub.sh
# 另外一个窗口
./sub.sh
3)文件内容
CMakeLists.txt、restart.sh与配置历史记录一致。
pub.sh、sub.sh不一致
Example.idl
module Example {
struct Message {
unsigned long id;
string<1024> text;
};
};
pub.sh
# /usr/bin
./build/DDSHelloWorldPublisher $(pwd)/src/total.xml
sub.sh
# /usr/bin
./build/DDSHelloWorldSubscriber $(pwd)/src/total.xml
total.xml
<?xml version="1.0" encoding="UTF-8" ?>
<dds xmlns="http://www.eprosima.com">
<profiles>
<domainparticipant_factory profile_name="domainparticipant_factory_profile">
<!-- ... -->
</domainparticipant_factory>
<participant profile_name="participant_profile">
<!-- ... -->
</participant>
<publisher profile_name="publisher_profile">
<!-- ... -->
</publisher>
<subscriber profile_name="subscriber_profile">
<!-- ... -->
</subscriber>
<data_writer profile_name="datawriter_profile">
<!-- ... -->
<topic>
<historyQos>
<kind>KEEP_LAST</kind>
<depth>5</depth>
</historyQos>
</topic>
<qos>
<reliability>
<kind>RELIABLE</kind>
<max_blocking_time>
<sec>2</sec>
</max_blocking_time>
</reliability>
<durability>
<kind>TRANSIENT_LOCAL</kind>
</durability>
</qos>
</data_writer>
<data_reader profile_name="datareader_profile">
<!-- ... -->
<topic>
<historyQos>
<kind>KEEP_LAST</kind>
<depth>5</depth>
</historyQos>
</topic>
<qos>
<reliability>
<kind>RELIABLE</kind>
<max_blocking_time>
<sec>2</sec>
</max_blocking_time>
</reliability>
<durability>
<kind>TRANSIENT_LOCAL</kind>
</durability>
</qos>
</data_reader>
<topic profile_name="topic_profile">
<!-- ... -->
</topic>
<transport_descriptors>
<!-- ... -->
</transport_descriptors>
</profiles>
<library_settings>
<intraprocess_delivery>FULL</intraprocess_delivery> <!-- OFF | USER_DATA_ONLY | FULL -->
</library_settings>
<log>
<!-- ... -->
</log>
<types>
<!-- ... -->
</types>
</dds>
HelloWorldPublisher.cpp
#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <string>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/DataWriterListener.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>
using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyPubListener : public DataWriterListener
{
public:
MyPubListener()
: matched_(0)
{
}
~MyPubListener() override
{
}
void on_publication_matched(
DataWriter *,
const PublicationMatchedStatus &info) override
{
if (info.current_count_change == 1)
{
matched_ = info.total_count;
std::cout << "Publisher matched." << std::endl;
}
else if (info.current_count_change == -1)
{
matched_ = info.total_count;
std::cout << "Publisher unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for PublicationMatchedStatus current count change." << std::endl;
}
}
std::atomic_int matched_;
};
int main(int argc, char *argv[])
{
std::string xml_filename = argv[1];
MyPubListener Mypl;
DomainParticipant *participant;
Topic *topic;
Publisher *publisher;
DataWriter *writer;
if (ReturnCode_t::RETCODE_OK ==
DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
{
participant =
DomainParticipantFactory::get_instance()->create_participant_with_profile(
0, "participant_profile");
TypeSupport type(new Example::MessagePubSubType());
type.register_type(participant);
topic = participant->create_topic("MessageTopic", type.get_type_name(), TOPIC_QOS_DEFAULT);
publisher = participant->create_publisher_with_profile("publisher_profile");
writer = publisher->create_datawriter_with_profile(topic, "datawriter_profile", &Mypl);
}
else
{
std::cout << "load_XML_profiles_file failed" << std::endl;
}
// 发布数据
Example::Message msg;
std::string str;
str.assign(1024, 'A');
msg.text(str);
for (long i = 1; i <= 10; ++i)
{
while (Mypl.matched_ == 0)
{
std::cout << "wait" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
msg.id(i);
writer->write(&msg);
std::cout << "Published message with id: " << i << std::endl;
// 稍微带点时延,否则容易直接丢弃部分数据,历史记录来不及存储
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
// 清理资源
publisher->delete_datawriter(writer);
participant->delete_publisher(publisher);
participant->delete_topic(topic);
DomainParticipantFactory::get_instance()->delete_participant(participant);
return 0;
}
HelloWorldSubscriber.cpp
#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>
const long PORT = 12345;
using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyReaderListener : public DataReaderListener
{
public:
void on_subscription_matched(
DataReader *reader,
const SubscriptionMatchedStatus &info) override
{
if (info.current_count_change == 1)
{
std::cout << "Subscriber matched." << std::endl;
}
else if (info.current_count_change == -1)
{
std::cout << "Subscriber unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
}
}
void on_data_available(DataReader *reader) override
{
Example::Message msg;
SampleInfo info;
if (reader->take_next_sample(&msg, &info) == ReturnCode_t::RETCODE_OK)
{
if (info.valid_data)
{
std::cout << "Received message with id: " << msg.id() << "text size:" << msg.text().size() << std::endl;
}
}
}
};
int main(int argc, char *argv[])
{
std::string xml_filename = argv[1];
DomainParticipant *participant;
Topic *topic;
Subscriber *subscriber;
DataReader *reader;
if (ReturnCode_t::RETCODE_OK ==
DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
{
participant =
DomainParticipantFactory::get_instance()->create_participant_with_profile(
0, "participant_profile");
TypeSupport type(new Example::MessagePubSubType());
type.register_type(participant);
topic = participant->create_topic("MessageTopic", type.get_type_name(), TOPIC_QOS_DEFAULT);
subscriber = participant->create_subscriber_with_profile("subscriber_profile");
reader = subscriber->create_datareader_with_profile(topic, "datareader_profile", new MyReaderListener());
}
else
{
std::cout << "load_XML_profiles_file failed" << std::endl;
}
static int count = 0;
// 等待接收消息
while (count <= 10)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
count++;
}
// 清理资源
subscriber->delete_datareader(reader);
participant->delete_subscriber(subscriber);
participant->delete_topic(topic);
DomainParticipantFactory::get_instance()->delete_participant(participant);
return 0;
}
4)运行结果
1.2.4.测试框架
在上一个基础上增加了自定义生成不同大小idl的工作
1)目录结构
.
├── CMakeLists.txt
├── pub.sh
├── sub.sh
├── gen.sh
├── Readme.md(非必要)
├── restart.sh
└── src
├── Example.idl
├── toal.xml
├── HelloWorldPublisher.cpp
└── HelloWorldSubscriber.cpp
2)测试时执行
chmod +x ./restart.sh
chmod +x ./pub.sh
chmod +x ./sub.sh
# 修改Example.idl中的sequence的大小
# 修改pub.sh中的PACK变量
# 在src下根据idl文件生成所需文件
./gen.sh
# 本窗口
./pub.sh
# 另外一个窗口
./sub.sh
3)文件内容
CMakeLists.txt、restart.sh与配置历史记录一致。
Example.idl
module Example {
struct Message {
unsigned long id;
sequence<char,1024*1024*1024> text;
};
};
gen.sh
#!/bin/bash
cd src &&
rm Example.h Example.cxx ExampleC* ExampleP*
fastddsgen Example.idl &&
cd .. &&
./restart.sh
config.ini
[settings]
debug = true
pub.sh
#!/bin/bash
# 配置文件路径
CONFIG_FILE="config.ini"
# 读取配置文件中的 debug 选项
DEBUG=$(grep -E '^\s*debug\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
if $DEBUG -eq true; then
echo "debug: $DEBUG"
fi
# 检查参数个数
TOPIC=topic_default
NUM_PACKETS=10
PACK=$((1024*1024*1024)) #根据Example.idl更改
if [ "$#" -ne 3 ]; then
echo "Usage: $0 <topic> <num_packets> <packet_size>"
else
# 获取参数
TOPIC=$1
NUM_PACKETS=$2
PACK=$3
fi
echo "topic: $TOPIC, num_packets: $NUM_PACKETS, packet_size: $PACK"
# 启动发布者
./build/DDSHelloWorldPublisher $(pwd)/src/total.xml $TOPIC $NUM_PACKETS $PACK
sub.sh
#!/bin/bash
# 检查参数个数
TOPIC=topic_default
NUM_PACKETS=10
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <topic> <num_packets>"
else
# 获取参数
TOPIC=$1
NUM_PACKETS=$2
fi
# 启动订阅者
./build/DDSHelloWorldSubscriber $(pwd)/src/total.xml $TOPIC $NUM_PACKETS
HelloWorldPublisher.cpp
#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
// #include <string>
#include <signal.h>
#include <unistd.h>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/DataWriterListener.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>
using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyPubListener : public DataWriterListener
{
public:
MyPubListener()
: matched_(0)
{
}
~MyPubListener() override
{
}
void on_publication_matched(
DataWriter *,
const PublicationMatchedStatus &info) override
{
if (info.current_count_change == 1)
{
matched_ = info.current_count;
std::cout << "matched: " << matched_ << std::endl;
std::cout << "Publisher matched." << std::endl;
}
else if (info.current_count_change == -1)
{
matched_ = info.current_count;
std::cout << "matched: " << matched_ << std::endl;
std::cout << "Publisher unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for PublicationMatchedStatus current count change." << std::endl;
}
}
std::atomic_int matched_;
};
class Me_
{
public:
Me_()
{
std::cout << "before main " << std::endl;
}
~Me_()
{
std::cout << "after main" << std::endl;
}
char get_a()
{
return a;
}
void set_a(char a_)
{
a = a_;
}
private:
char a;
};
Me_ me;
DomainParticipant *participant = nullptr;
Topic *topic = nullptr;
Publisher *publisher = nullptr;
DataWriter *writer = nullptr;
void signal_int_handler(int signum)
{
if (signum == SIGINT)
{
if (writer != nullptr)
publisher->delete_datawriter(writer);
if (publisher != nullptr)
participant->delete_publisher(publisher);
if (topic != nullptr)
participant->delete_topic(topic);
if (participant != nullptr)
DomainParticipantFactory::get_instance()->delete_participant(participant);
exit(0);
}
}
int main(int argc, char *argv[])
{
// Ctrl+C处理
std::cout << "Entering main function" << std::endl;
std::cout << "argc: " << argc << std::endl;
for (int i = 0; i < argc; ++i)
{
std::cout << "argv[" << i << "]: " << argv[i] << std::endl;
}
signal(SIGINT, signal_int_handler);
std::string xml_filename = argv[1];
std::string topic_name = argv[2];
long NUM_PACKAGE = std::stoi(argv[3]);
long PACKAGE_SIZE = std::stoi(argv[4]);
long COUNT = 0;
MyPubListener Mypl;
// 未做错误处理
if (ReturnCode_t::RETCODE_OK ==
DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
{
participant =
DomainParticipantFactory::get_instance()->create_participant_with_profile(
0, "participant_profile");
TypeSupport type(new Example::MessagePubSubType());
type.register_type(participant);
topic = participant->create_topic(topic_name, type.get_type_name(), TOPIC_QOS_DEFAULT);
publisher = participant->create_publisher_with_profile("publisher_profile");
writer = publisher->create_datawriter_with_profile(topic, "datawriter_profile", &Mypl);
}
else
{
std::cout << "load_XML_profiles_file failed" << std::endl;
}
// 发布数据
Example::Message msg;
for (long i = 1; i <= NUM_PACKAGE; ++i)
{
while (Mypl.matched_ <= 0)
{
std::cout << "wait" << std::endl;
COUNT++;
if (COUNT >= 20)
{
std::cout << "timeout" << std::endl;
goto out;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "mypl.matched_: " << Mypl.matched_ << std::endl;
msg.id(i);
me.set_a('A' + i % 26);
std::vector<char> str;
if(PACKAGE_SIZE>=i) {
str = std::vector<char>(PACKAGE_SIZE - i, me.get_a());
}
msg.text(str);
writer->write(&msg);
std::cout<<(msg.text())[0]<<std::endl;
std::cout << "Published message with id: " << i << std::endl;
// 稍微带点时延,否则容易直接丢弃部分数据,历史记录来不及存储
std::this_thread::sleep_for(std::chrono::seconds(1));
}
out:
// 清理资源
publisher->delete_datawriter(writer);
participant->delete_publisher(publisher);
participant->delete_topic(topic);
DomainParticipantFactory::get_instance()->delete_participant(participant);
return 0;
}
HelloWorldSubscriber.cpp
#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <string>
#include <signal.h>
#include <unistd.h>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>
using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyReaderListener : public DataReaderListener
{
public:
MyReaderListener()
: messageCount(0), matched_(0)
{
}
void on_subscription_matched(
DataReader *reader,
const SubscriptionMatchedStatus &info) override
{
if (info.current_count_change == 1)
{
matched_ = info.current_count;
std::cout << "Subscriber matched." << std::endl;
}
else if (info.current_count_change == -1)
{
matched_ = info.current_count;
std::cout << "Subscriber unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
}
}
void on_data_available(DataReader *reader) override
{
Example::Message msg;
SampleInfo info;
if (reader->take_next_sample(&msg, &info) == ReturnCode_t::RETCODE_OK)
{
if (info.valid_data)
{
messageCount++;
std::cout << "Received message with id: " << msg.id() << "text size:" << msg.text().size() << std::endl;
std::cout << "First char: " << (msg.text())[0] << "..." << std::endl;
}
}
}
std::atomic_int messageCount;
std::atomic_int matched_;
};
DomainParticipant *participant;
Topic *topic;
Subscriber *subscriber;
DataReader *reader;
MyReaderListener *listener = new MyReaderListener();
TypeSupport type(new Example::MessagePubSubType());
void cleanup_resources()
{
if (reader != nullptr)
subscriber->delete_datareader(reader);
if (subscriber != nullptr)
participant->delete_subscriber(subscriber);
if (topic != nullptr)
participant->delete_topic(topic);
if (participant != nullptr)
DomainParticipantFactory::get_instance()->delete_participant(participant);
delete listener;
}
void signal_int_handler(int signum)
{
if (signum == SIGINT)
{
cleanup_resources();
exit(0);
}
}
int main(int argc, char *argv[])
{
std::cout << "Entering main function" << std::endl;
std::cout << "argc: " << argc << std::endl;
for (int i = 0; i < argc; ++i)
{
std::cout << "argv[" << i << "]: " << argv[i] << std::endl;
}
if (argc < 4)
{
std::cerr << "Usage: " << argv[0] << " <xml_filename>" << std::endl;
return 0; // 返回错误码
}
// Ctrl+C处理
signal(SIGINT, signal_int_handler);
std::string xml_filename = argv[1];
std::string topic_name = argv[2];
int NUM_PACKAGE = std::stoi(argv[3]);
int COUNT = 0;
if (ReturnCode_t::RETCODE_OK ==
DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
{
participant =
DomainParticipantFactory::get_instance()->create_participant_with_profile(
0, "participant_profile");
type.register_type(participant);
topic = participant->create_topic_with_profile(topic_name, type.get_type_name(), "topic_profile");
if (topic == nullptr)
{
std::cout << "create_topic failed" << std::endl;
}
subscriber = participant->create_subscriber_with_profile("subscriber_profile");
if (subscriber == nullptr)
{
std::cout << "create_subscriber failed" << std::endl;
}
reader = subscriber->create_datareader_with_profile(topic, "datareader_profile", listener);
if (reader == nullptr)
{
std::cout << "create_datareader failed" << std::endl;
}
}
else
{
std::cout << "load_XML_profiles_file failed" << std::endl;
}
std::cout << "Waiting for Data..." << std::endl;
// 等待接收消息
while (listener->messageCount < NUM_PACKAGE && COUNT <= 20)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
if (listener->matched_ == 0)
{
COUNT++;
}
std::cout << "wait" << std::endl;
}
// 清理资源
cleanup_resources();
return 0;
}
total.xml
<?xml version="1.0" encoding="UTF-8" ?>
<dds xmlns="http://www.eprosima.com">
<profiles>
<domainparticipant_factory profile_name="domainparticipant_factory_profile">
<!-- ... -->
</domainparticipant_factory>
<participant profile_name="participant_profile">
<!-- ... -->
</participant>
<publisher profile_name="publisher_profile">
<!-- ... -->
</publisher>
<subscriber profile_name="subscriber_profile">
<!-- ... -->
</subscriber>
<data_writer profile_name="datawriter_profile">
<!-- ... -->
</data_writer>
<data_reader profile_name="datareader_profile">
<!-- ... -->
</data_reader>
<topic profile_name="topic_profile">
<!-- ... -->
</topic>
<transport_descriptors>
<!-- ... -->
</transport_descriptors>
</profiles>
<library_settings>
<intraprocess_delivery>OFF</intraprocess_delivery> <!-- OFF | USER_DATA_ONLY | FULL(default) -->
</library_settings>
<log>
<!-- ... -->
</log>
<types>
<!-- ... -->
</types>
</dds>
4)测试结果
1.2.5.完整测试框架
这一项需要Python环境
在测试框架的基础上,加上CPU占用,内存占用数据的提取,并可以实现自定义数据包大小的填充,
因为这是一个完整的框架,所以在此基础上会附上所有文件的代码,
因为需要自定义数据包,Example.idl
会在start_test.sh
中自动生成,故目录结构中不再列出
1)目录结构
.
├── CMakeLists.txt
├── config.ini
├── idls
│ └── gen_idl.sh
├── monitor.py
├── pub.sh
├── Readme.md(非必要)
├── restart.sh
├── result
│ ├── img
│ └── log
├── src
│ ├── HelloWorldPublisher.cpp
│ ├── HelloWorldSubscriber.cpp
│ └── total.xml
├── start_test.sh
└── sub.sh
2)测试时执行
./start_test.sh
# 执行过程中需要根据需求按y/n继续
# 更改config.ini中的数据量时,需要重新覆盖,并重新编译
Do you want to overwrite it? (y/n): y
Do you want to recompile it? (recommend)(y/n):y
3)文件内容
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(DDSHelloWorld)
# 添加可调式选项
set(CMAKE_BUILD_TYPE Debug)
if(NOT fastrtps_FOUND)
find_package(fastrtps REQUIRED)
endif()
if(NOT fastcdr_FOUND)
find_package(fastcdr REQUIRED)
endif()
# Set c++11
include(CheckCXXCompilerFlag)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG OR
CMAKE_CXX_COMPILER_ID MATCHES "Clang")
check_cxx_compiler_flag(-std=c++11 SUPPORTS_CXX11)
if(SUPPORTS_CXX11)
add_compile_options(-std=c++11)
else()
message(FATAL_ERROR "Compiler doesn't support C++11")
endif()
endif()
message(STATUS "Configuring HelloWorld publisher/subscriber example...")
# 注意 将src/*.cxx添加到DDS_HELLOWORLD_SOURCES_CXX变量中
file(GLOB DDS_HELLOWORLD_SOURCES_CXX "src/*.cxx")
add_executable(DDSHelloWorldPublisher src/HelloWorldPublisher.cpp ${DDS_HELLOWORLD_SOURCES_CXX})
target_link_libraries(DDSHelloWorldPublisher fastrtps fastcdr)
add_executable(DDSHelloWorldSubscriber src/HelloWorldSubscriber.cpp ${DDS_HELLOWORLD_SOURCES_CXX})
target_link_libraries(DDSHelloWorldSubscriber fastrtps fastcdr)
config.ini
# 配置文件中大项一律大写,子项一律小写
[DEBUG]
debug=true
[LAUNCH]
num_group=1
num_pub=1
num_packets_pub=50
num_sub=1
num_packets_sub=50
# SIZE_PACKET 1024 1024*100 1024*1024 1024*1024*100 1024*1024*1024 可自定义
size_packet=1024*1024*100
xml_file=src/total.xml
# ROLE pub sub 二选一
role=sub
gen_idl.sh
fastddsgen idl_*/Example.idl
montior.py
import os
import time
import psutil
import matplotlib.pyplot as plt
import argparse
from concurrent.futures import ThreadPoolExecutor
def get_process_info(pid):
try:
process = psutil.Process(pid)
cpu_percent = process.cpu_percent(interval=1)
memory_info = process.memory_info()
memory_usage = memory_info.rss / (1024 * 1024) # Convert to MB
return cpu_percent, memory_usage
except psutil.NoSuchProcess:
return None, None
def monitor_process(pid, interval=1, duration=60):
cpu_data = []
memory_data = []
timestamps = []
start_time = time.time()
while time.time() - start_time < duration:
current_time = time.strftime('%H:%M:%S', time.localtime())
timestamps.append(current_time)
cpu, memory = get_process_info(pid)
if cpu is not None and memory is not None:
cpu_data.append(cpu)
memory_data.append(memory)
else:
cpu_data.append(None)
memory_data.append(None)
time.sleep(interval)
return timestamps, {pid: cpu_data}, {pid: memory_data} # 返回字典
def plot_data(timestamps, cpu_data, memory_data, pid, output_file):
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
# 过滤掉 None 值
filtered_timestamps = [t for t, c in zip(timestamps, cpu_data[pid]) if c is not None]
filtered_cpu_data = [c for c in cpu_data[pid] if c is not None]
filtered_memory_data = [m for m in memory_data[pid] if m is not None]
ax1.plot(filtered_timestamps, filtered_cpu_data)
ax1.set_title(f'CPU Usage Over Time (PID {pid})')
ax1.set_xlabel('Time')
ax1.set_ylabel('CPU Usage (%)')
ax2.plot(filtered_timestamps, filtered_memory_data)
ax2.set_title(f'Memory Usage Over Time (PID {pid})')
ax2.set_xlabel('Time')
ax2.set_ylabel('Memory Usage (MB)')
plt.tight_layout()
plt.savefig(output_file)
plt.close(fig)
def get_pids_with_keyword(keyword, process_type):
pids = []
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
try:
if keyword in proc.name() or (proc.cmdline() and keyword in ' '.join(proc.cmdline())):
if process_type in proc.name() or (proc.cmdline() and process_type in ' '.join(proc.cmdline())):
pids.append(proc.pid)
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
continue
return pids
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Monitor CPU and memory usage of DDS processes.")
parser.add_argument("--interval", type=int, default=1, help="Sampling interval in seconds (default: 1)")
parser.add_argument("--total_time", type=int, default=10, help="Total monitoring time in seconds (default: 10)")
parser.add_argument("--size", type=int, default=10, help="package_size ONLY USED TO mkdir (default: 10)")
args = parser.parse_args()
# 获取包含 DDS 关键字的 pub 和 sub 进程的 PID
pub_pids = get_pids_with_keyword('DDS', 'Pub')
sub_pids = get_pids_with_keyword('DDS', 'Sub')
all_pids = pub_pids + sub_pids
if not all_pids:
print("No DDS processes found.")
exit(1)
# 使用 ThreadPoolExecutor 并行监控进程
with ThreadPoolExecutor(max_workers=len(all_pids)) as executor:
futures = {executor.submit(monitor_process, pid, args.interval, args.total_time): pid for pid in all_pids}
results = {pid: future.result() for future, pid in futures.items()}
img_dir = os.path.join('./result/img', str(args.size))
os.makedirs(img_dir, exist_ok=True)
# 绘制并保存数据
for pid, (timestamps, cpu_data, memory_data) in results.items():
if pid in pub_pids:
output_file = os.path.join(img_dir, f'pub_{pid}.png')
plot_data(timestamps, cpu_data, memory_data, pid, output_file)
elif pid in sub_pids:
output_file = os.path.join(img_dir, f'sub_{pid}.png')
plot_data(timestamps, cpu_data, memory_data, pid, output_file)
pub.sh
#!/bin/bash
# 配置文件路径
CONFIG_FILE="config.ini"
# 检查配置文件是否存在
if [ ! -f "$CONFIG_FILE" ]; then
echo "Config file not found: $CONFIG_FILE"
exit 1
fi
# 读取配置文件中的 debug 选项
DEBUG=$(grep -E '^\s*debug\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
# 检查参数个数
XML_FILE=$(pwd)/src/total.xml
TOPIC=topic_default
NUM_PACKETS=10
PACK=$((1024))
echo "$#"
MAX_PARAMS=10
if [ "$#" -ne 4 ]; then
if $DEBUG -eq true; then
echo "Usage: $0 <xml_file> <topic> <num_packets> <packet_size>"
# 打印传入参数
for ((i=1; i<=MAX_PARAMS && i<=$#; i++)); do
eval param=\$$i
echo "Parameter $i: $param"
done
fi
else
# 获取参数
XML_FILE=$1
TOPIC=$2
NUM_PACKETS=$3
PACK=$4
fi
if $DEBUG -eq true; then
echo "xml: $XML_FILE, topic: $TOPIC, num_packets: $NUM_PACKETS, packet_size: $PACK"
fi
# 切换到 build 目录
cd build
# 启动发布者
./DDSHelloWorldPublisher $XML_FILE $TOPIC $NUM_PACKETS $PACK
restart.sh
# /usr/bin
rm -rf ./build
mkdir build
cd build
cmake ..
cmake --build .
start_test.sh
#!/bin/bash
# 读取配置文件中的参数
CONFIG_FILE="config.ini"
# 检查配置文件是否存在
if [ ! -f "$CONFIG_FILE" ]; then
echo "Config file not found: $CONFIG_FILE"
exit 1
fi
# 默认参数
DEBUG=$(grep -E '^\s*debug\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
NUM_GROUP=$(grep -E '^\s*num_group\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
NUM_PUB=$(grep -E '^\s*num_pub\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
NUM_PACKETS_PUB=$(grep -E '^\s*num_packets_pub\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
NUM_SUB=$(grep -E '^\s*num_sub\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
NUM_PACKETS_SUB=$(grep -E '^\s*num_packets_sub\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
SIZE_PACKET=$(grep -E '^\s*size_packet\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
XML_FILE=$(pwd)/$(grep -E '^\s*xml_file\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
ROLE=$(grep -E '^\s*role\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
# 迁移idl库并重新编译
M_SIZE=$((SIZE_PACKET))
if $DEBUG -eq true; then
echo "DEBUG: $DEBUG"
echo "NUM_GROUP: $NUM_GROUP"
echo "NUM_PUB: $NUM_PUB"
echo "NUM_PACKETS_PUB: $NUM_PACKETS_PUB"
echo "NUM_SUB: $NUM_SUB"
echo "NUM_PACKETS_SUB: $NUM_PACKETS_SUB"
echo "SIZE_PACKET: $SIZE_PACKET"
echo "XML_FILE: $XML_FILE"
echo "ROLE: $ROLE"
echo "M_SIZE: $M_SIZE"
fi
SOURCE_FILE="$(pwd)/idls/idl_$M_SIZE"
DEST_FILE="$(pwd)/src/"
# 循环询问用户是否覆盖
while true; do
read -p "Destination file already exists: $DEST_FILE. Do you want to overwrite it? (y/n): " choice
case "$choice" in
y|Y )
CONTENT="module Example {\n\t
struct Message {\n\t\t
unsigned long id;\n\t\t
sequence<char,$M_SIZE> text;\n\t
};\n
};\n
"
mkdir -p "$SOURCE_FILE"
echo -e $CONTENT > "$SOURCE_FILE"/Example.idl
fastddsgen "$SOURCE_FILE/Example.idl"
cp -f "$SOURCE_FILE"/* "$DEST_FILE"
echo "Files copied and overwritten."
break
;;
n|N )
echo "Files not copied.Use previous config."
break
;;
* )
echo "Invalid input. Please enter 'y' or 'n'."
;;
esac
done
# 重新编译
while true; do
read -p "Do you want to recompile it? (recommend)(y/n): " choice
case "$choice" in
y|Y )
./restart.sh
echo "Files recompiled."
break
;;
n|N )
echo "Warning: No files recompiled.Files copied but not make sense."
break
;;
* )
echo "Invalid input. Please enter 'y' or 'n'."
;;
esac
done
# 检查参数个数
if $DEBUG -eq true; then
if [ "$#" -ne 6 ]; then
echo "Usage: $0 <num_group> <num_pub> <num_packets_pub> <num_sub> <num_packets_sub> <size_packet>"
echo "use default params:"
echo "Please find in config.ini."
fi
echo "----------"
fi
mkdir -p ./result/log/$((SIZE_PACKET))
echo $ROLE
for ((i=1; i<=NUM_GROUP; i++)); do
if [[ "$ROLE" == "pub" ]]; then
# 启动发布者
for ((j=1; j<=NUM_PUB; j++)); do
(./pub.sh $XML_FILE "Topic_${i}" $NUM_PACKETS_PUB $((SIZE_PACKET))) > ./result/log/$((SIZE_PACKET))/pub_${i}_${j}.log &
done
elif [[ "$ROLE" == "sub" ]]; then
# 启动订阅者
for ((j=1; j<=NUM_SUB; j++)); do
(./sub.sh $XML_FILE "Topic_${i}" $NUM_PACKETS_SUB) > ./result/log/$((SIZE_PACKET))/sub_${i}_${j}.log &
done
fi
done
echo "Running ... Result in result directory."
python3 monitor.py --total_time=$(($NUM_PACKETS_PUB*2)) --size=$((SIZE_PACKET)) &
# 等待所有子进程完成
wait
echo "All processes have finished."
sub.sh
#!/bin/bash
# 配置文件路径
CONFIG_FILE="config.ini"
# 检查配置文件是否存在
if [ ! -f "$CONFIG_FILE" ]; then
echo "Config file not found: $CONFIG_FILE"
exit 1
fi
# 读取配置文件中的 debug 选项
DEBUG=$(grep -E '^\s*debug\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
# 检查参数个数
TOPIC=topic_default
NUM_PACKETS=10
XML_FILE=$(pwd)/src/total.xml
if [ "$#" -ne 3 ]; then
if $DEBUG -eq true; then
echo "Usage: $0 <xml_file> <topic> <num_packets>"
fi
else
# 获取参数
XML_FILE=$1
TOPIC=$2
NUM_PACKETS=$3
fi
# 切换到 build 目录
cd build
# 启动订阅者
./DDSHelloWorldSubscriber $XML_FILE $TOPIC $NUM_PACKETS
HelloWorldPublisher.cpp
#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <signal.h>
#include <unistd.h>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/DataWriterListener.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>
using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyPubListener : public DataWriterListener
{
public:
MyPubListener()
: matched_(0)
{
}
~MyPubListener() override
{
}
void on_publication_matched(
DataWriter*,
const PublicationMatchedStatus& info) override
{
if (info.current_count_change == 1)
{
matched_ = info.current_count;
std::cout << "matched: " << matched_ << std::endl;
std::cout << "Publisher matched." << std::endl;
}
else if (info.current_count_change == -1)
{
matched_ = info.current_count;
std::cout << "matched: " << matched_ << std::endl;
std::cout << "Publisher unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for PublicationMatchedStatus current count change." << std::endl;
}
}
std::atomic_int matched_;
};
class Me_{
public:
Me_()
{
std::cout<<"before main "<<std::endl;
}
~Me_()
{
std::cout<<"after main"<<std::endl;
}
char get_a()
{
return a;
}
void set_a(char a_)
{
a=a_;
}
private:
char a;
};
Me_ me;
DomainParticipant* participant=nullptr;
Topic* topic=nullptr;
Publisher* publisher=nullptr;
DataWriter* writer=nullptr;
void signal_int_handler(int signum)
{
if(signum == SIGINT)
{
if(writer!=nullptr)
publisher->delete_datawriter(writer);
if(publisher!=nullptr)
participant->delete_publisher(publisher);
if(topic!=nullptr)
participant->delete_topic(topic);
if(participant!=nullptr)
DomainParticipantFactory::get_instance()->delete_participant(participant);
exit(0);
}
}
int main(int argc, char* argv[])
{
//Ctrl+C处理
std::cout << "Entering main function" << std::endl;
std::cout << "argc: " << argc << std::endl;
for (int i = 0; i < argc; ++i) {
std::cout << "argv[" << i << "]: " << argv[i] << std::endl;
}
signal(SIGINT, signal_int_handler);
std::string xml_filename = argv[1];
std::string topic_name = argv[2];
long NUM_PACKAGE = std::stoi(argv[3]);
long PACKAGE_SIZE = std::stoi(argv[4]);
long COUNT = 0;
MyPubListener Mypl;
//未做错误处理
if (ReturnCode_t::RETCODE_OK ==
DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
{
participant =
DomainParticipantFactory::get_instance()->create_participant_with_profile(
0, "participant_profile");
TypeSupport type(new Example::MessagePubSubType());
type.register_type(participant);
topic = participant->create_topic(topic_name, type.get_type_name(), TOPIC_QOS_DEFAULT);
publisher = participant->create_publisher_with_profile("publisher_profile");
writer = publisher->create_datawriter_with_profile(topic, "datawriter_profile",&Mypl);
}
else
{
std::cout << "load_XML_profiles_file failed" << std::endl;
}
// 发布数据
Example::Message msg;
for (long i = 1; i <= NUM_PACKAGE; ++i) {
while(Mypl.matched_<=0)
{
std::cout<<"wait"<<std::endl;
COUNT++;
if(COUNT>=100)
{
std::cout<<"timeout"<<std::endl;
goto out;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout<<"mypl.matched_: "<<Mypl.matched_<<std::endl;
msg.id(i);
me.set_a('A'+i%26);
std::vector<char> str;
if(PACKAGE_SIZE>=i) {
str = std::vector<char>(PACKAGE_SIZE - i, me.get_a());
}
msg.text(str);
writer->write(&msg);
std::cout << "Published message with id: " << i << std::endl;
//稍微带点时延,否则容易直接丢弃部分数据,历史记录来不及存储
std::this_thread::sleep_for(std::chrono::seconds(1));
}
out:
// 清理资源
publisher->delete_datawriter(writer);
participant->delete_publisher(publisher);
participant->delete_topic(topic);
DomainParticipantFactory::get_instance()->delete_participant(participant);
return 0;
}
HelloWorldSubscriber.cpp
#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <string>
#include <signal.h>
#include <unistd.h>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>
using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyReaderListener : public DataReaderListener
{
public:
MyReaderListener()
: messageCount(0)
, matched_(0)
{
}
void on_subscription_matched(
DataReader* reader,
const SubscriptionMatchedStatus& info) override
{
if (info.current_count_change == 1)
{
matched_=info.current_count;
std::cout << "Subscriber matched." << std::endl;
}
else if (info.current_count_change == -1)
{
matched_=info.current_count;
std::cout << "Subscriber unmatched." << std::endl;
}
else
{
std::cout << info.current_count_change
<< " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
}
}
void on_data_available(DataReader* reader) override {
Example::Message msg;
SampleInfo info;
if (reader->take_next_sample(&msg, &info) == ReturnCode_t::RETCODE_OK) {
if (info.valid_data) {
messageCount++;
std::cout << "Received message with id: " << msg.id() <<"text size:"<<msg.text().size()<< std::endl;
std::cout << "First char: "<<(msg.text())[0]<<"..."<<std::endl;
} else {
std::cout<<"invalid_data"<<std::endl;
}
} else {
std::cout<<"take_next_sample ERROR"<<std::endl;
}
}
std::atomic_int messageCount;
std::atomic_int matched_;
};
DomainParticipant* participant;
Topic* topic;
Subscriber* subscriber;
DataReader* reader;
MyReaderListener* listener = new MyReaderListener();
TypeSupport type(new Example::MessagePubSubType());
void cleanup_resources()
{
if(reader != nullptr)
subscriber->delete_datareader(reader);
if(subscriber != nullptr)
participant->delete_subscriber(subscriber);
if(topic != nullptr)
participant->delete_topic(topic);
if(participant != nullptr)
DomainParticipantFactory::get_instance()->delete_participant(participant);
delete listener;
}
void signal_int_handler(int signum)
{
if(signum == SIGINT)
{
cleanup_resources();
exit(0);
}
}
int main(int argc, char* argv[])
{
std::cout << "Entering main function" << std::endl;
std::cout << "argc: " << argc << std::endl;
for (int i = 0; i < argc; ++i) {
std::cout << "argv[" << i << "]: " << argv[i] << std::endl;
}
if (argc < 4) {
std::cerr << "Usage: " << argv[0] << " <xml_filename>" << std::endl;
return 0; // 返回错误码
}
//Ctrl+C处理
signal(SIGINT, signal_int_handler);
std::string xml_filename = argv[1];
std::string topic_name = argv[2];
int NUM_PACKAGE = std::stoi(argv[3]);
int COUNT=0;
if (ReturnCode_t::RETCODE_OK ==
DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
{
participant =
DomainParticipantFactory::get_instance()->create_participant_with_profile(
0, "participant_profile");
type.register_type(participant);
topic = participant->create_topic_with_profile(topic_name, type.get_type_name(), "topic_profile");
if(topic==nullptr)
{
std::cout << "create_topic failed" << std::endl;
}
subscriber = participant->create_subscriber_with_profile("subscriber_profile");
if(subscriber==nullptr)
{
std::cout << "create_subscriber failed" << std::endl;
}
reader = subscriber->create_datareader_with_profile(topic, "datareader_profile",listener);
if(reader==nullptr)
{
std::cout << "create_datareader failed" << std::endl;
}
}
else
{
std::cout << "load_XML_profiles_file failed" << std::endl;
}
std::cout << "Waiting for Data..." << std::endl;
// 等待接收消息
while (listener->messageCount<NUM_PACKAGE&&COUNT<=100) {
std::this_thread::sleep_for(std::chrono::seconds(1));
if(listener->matched_==0)
{
COUNT++;
}
else{
COUNT = 0;
}
std::cout<<"wait"<<COUNT<<std::endl;
}
// 清理资源
cleanup_resources();
return 0;
}
total.xml
<?xml version="1.0" encoding="UTF-8" ?>
<dds xmlns="http://www.eprosima.com">
<profiles>
<domainparticipant_factory profile_name="domainparticipant_factory_profile">
<!-- ... -->
</domainparticipant_factory>
<participant profile_name="participant_profile">
<!-- ... -->
</participant>
<publisher profile_name="publisher_profile">
<!-- ... -->
</publisher>
<subscriber profile_name="subscriber_profile">
<!-- ... -->
</subscriber>
<data_writer profile_name="datawriter_profile">
<!-- ... -->
</data_writer>
<data_reader profile_name="datareader_profile">
<!-- ... -->
</data_reader>
<topic profile_name="topic_profile">
<!-- ... -->
</topic>
<transport_descriptors>
<!-- ... -->
</transport_descriptors>
</profiles>
<library_settings>
<intraprocess_delivery>FULL</intraprocess_delivery>
</library_settings>
<log>
<!-- ... -->
</log>
<types>
<!-- ... -->
</types>
</dds>
4) 测试结果
/log/<数据量大小>
下的两个日志文件的内容如下表
pub_1_1 | sub_1_1 |
---|---|
xml: /home/gh/workspace/Testfastdds5_test_v3_bak2/src/total.xml, topic: Topic_1, num_packets: 50, packet_size: 104857600 before main Entering main function argc: 5 argv[0]: ./DDSHelloWorldPublisher argv[1]: /home/gh/workspace/Testfastdds5_test_v3_bak2/src/total.xml argv[2]: Topic_1 argv[3]: 50 argv[4]: 104857600 matched: 1 Publisher matched. mypl.matched_: 1 Published message with id: 1 mypl.matched_: 1 Published message with id: 2 mypl.matched_: 1 | Entering main function argc: 4 argv[0]: ./DDSHelloWorldSubscriber argv[1]: /home/gh/workspace/Testfastdds5_test_v3_bak/src/total.xml argv[2]: Topic_1 argv[3]: 50 Waiting for Data… wait1 wait2 wait3 Subscriber matched. wait0 Received message with id: 1text size:104857599 First char: B… wait0 Received message with id: 2text size:104857598 First char: C… |
/img/<数据量大小>
下有测试图像
Sub的测试结果
Pub的测试结果