本文目标是实现FastDDS的跨网段通信,其中配置仅在RTPS层可用,所以需要实现一个RTPS层的Demo,再次基础上进行配置,实现通信。
故本文使用FastDDSv2.14.2
版本的RTPS层进行发布订阅的简单例子,并在此基础上进行可跨网段通信的配置,使其完成通信。
1.RTPS简单Demo
FastDDS提供两层通信模型,DDS层和RTPS层,他们的对应关系如下,官方给出了参考的AsSocket
例子和Registered
例子。
DDS Layer | RTPS Layer |
---|---|
Domain | RTPSDomain |
DomainParticipant | RTPSParticipant |
DataWriter | RTPSWriter |
DataReader | RTPSReader |
1.1.环境说明
VMware的Ubuntu22.04LTS虚拟机*1
1.2.目录结构
项目目录结构如下,其中三个脚本文件*.sh
方便测试。RTPSPublisher.cc
,RTPSSubscriber.cc
分别是RTPS层的发布者代码文件和订阅者代码文件。
.
├── CMakeLists.txt
├── pub.sh
├── restart.sh
├── src
│ ├── RTPSPublisher.cc
│ └── RTPSSubscriber.cc
└── sub.sh
1.3.运行方式
# 编译
./restart.sh
# 运行
./pub.sh # 一个窗口
./sub.sh # 另一个窗口
1.4.执行结果
发布者发布完10条消息后自动结束,订阅者可以回车结束
1.5.文件内容
先解释各个文件的运行逻辑,最后补充代码部分
1.5.1.文件逻辑
- 总体部分
- 发布者和订阅者之间需要通过
Topic
名称的一致性进行匹配,之后再进行传输,两边约定的内容一致,在本文中指代HelloWorld
类。 - 代码运行时有时需要通过
Ctrl+C
中止程序,signal_int_handler
用于中止后的垃圾回收,即RTPSDomain::stopAll()
。 - 自定义监听器类用于生成具体的
Writer
/Reader
,方便对发布者状态进行跟踪
- 发布者和订阅者之间需要通过
- 发布者代码
- 自定义监听器类用于生成具体的
Writer
,其中需要重写onWriterMatched
,当发布者匹配到订阅者时调用该函数。 - 实体创建顺序如下图
- 生成HelloWorld实例,并将它们序列化为
CacheChange_t
对象添加到写历史中以进行发布。每次迭代都会打印一条消息,并休眠一秒。
- 自定义监听器类用于生成具体的
- 订阅者代码
- 自定义监听器类用于生成具体的
Reader
,其中需要重写onReaderMatched
和onNewCacheChangeAdded
,前者表示订阅者匹配到发布者时调用该函数,后者表示接收队列有元素添加时调用该函数,所以应该在第二个函数中写入读取的逻辑。 - 实体创建顺序如下图,与发布者大同小异
- 等待回车结束
- 自定义监听器类用于生成具体的
1.5.2.文件具体内容
restart.sh
文件内容
# /usr/bin
rm -rf ./build
mkdir build
cd build
cmake ..
cmake --build .
pub.sh
文件内容
# /usr/bin
./build/RTPSPublisher
sub.sh
文件内容
# /usr/bin
./build/RTPSSubscriber
CMakeLists.txt
文件内容
cmake_minimum_required(VERSION 3.20)
project(RTPSHelloWorld)
# 添加可调式选项
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(RTPSPublisher src/RTPSPublisher.cc ${DDS_HELLOWORLD_SOURCES_CXX})
target_link_libraries(RTPSPublisher fastrtps fastcdr)
add_executable(RTPSSubscriber src/RTPSSubscriber.cc ${DDS_HELLOWORLD_SOURCES_CXX})
target_link_libraries(RTPSSubscriber fastrtps fastcdr)
# 如果需要链接库或者包含目录,可以添加以下命令
# include_directories(include)
# target_link_libraries(${PROJECT_NAME} libname)
RTPSPublisher.cc
文件内容
#include <iostream>
#include <thread>
#include <chrono>
#include <signal.h>
#include <unistd.h>
#include <fastrtps/rtps/participant/RTPSParticipant.h>
#include <fastrtps/rtps/RTPSDomain.h>
#include <fastrtps/rtps/writer/RTPSWriter.h>
#include <fastrtps/rtps/common/CacheChange.h>
#include <fastrtps/rtps/history/WriterHistory.h>
#include <fastrtps/rtps/writer/WriterListener.h>
#include <fastrtps/rtps/attributes/RTPSParticipantAttributes.h>
#include <fastrtps/rtps/attributes/WriterAttributes.h>
#include <fastrtps/utils/fixed_size_string.hpp>
using namespace eprosima::fastrtps;
using namespace eprosima::fastrtps::rtps;
class MyListener : public WriterListener
{
public:
MyListener()
: n_matched(0)
{
}
~MyListener()
{
}
void onWriterMatched(
RTPSWriter *,
MatchingInfo &info) override
{
if (info.status == MATCHED_MATCHING)
{
++n_matched;
}
}
int n_matched;
private:
using eprosima::fastrtps::rtps::WriterListener::onWriterMatched;
} m_listener;
class HelloWorld
{
public:
int32_t index;
char message[100];
void set_index(int32_t _index)
{
index = _index;
}
int32_t get_index() const
{
return index;
}
void set_message(const char *_message)
{
strncpy(message, _message, sizeof(message) - 1);
message[sizeof(message) - 1] = '\0';
}
const char *get_message() const
{
return message;
}
};
void signal_int_handler(int signum)
{
if (signum == SIGINT)
{
RTPSDomain::stopAll();
exit(0);
}
}
int main()
{
signal(SIGINT, signal_int_handler);
RTPSParticipantAttributes PParam;
PParam.participantID = 0;
PParam.builtin.discovery_config.discoveryProtocol = DiscoveryProtocol::SIMPLE;
PParam.builtin.use_WriterLivelinessProtocol = true;
PParam.setName("publisherParticipant");
RTPSParticipant *participant = RTPSDomain::createParticipant(0, PParam);
if (participant == nullptr)
{
std::cerr << "Error creating participant" << std::endl;
return -1;
}
HistoryAttributes hatt;
hatt.payloadMaxSize = 255;
hatt.maximumReservedCaches = 50;
WriterHistory *history = new WriterHistory(hatt);
WriterAttributes WParam;
WParam.endpoint.topicKind = NO_KEY;
WParam.endpoint.reliabilityKind = BEST_EFFORT;
RTPSWriter *writer = RTPSDomain::createRTPSWriter(participant, WParam, history, &m_listener);
if (writer == nullptr)
{
std::cerr << "Error creating writer" << std::endl;
delete history;
RTPSDomain::removeRTPSParticipant(participant);
return -1;
}
std::cout << "Registering Writer" << std::endl;
// Topic
TopicAttributes Tatt;
Tatt.topicKind = NO_KEY;
Tatt.topicDataType = "string";
Tatt.topicName = "exampleTopic";
WriterQos Wqos;
if (!participant->registerWriter(writer, Tatt, Wqos))
{
std::cout << "ERROR: registerWriter" << std::endl;
return -1;
}
while (m_listener.n_matched == 0)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Not Matched" << std::endl;
}
HelloWorld hello;
uint32_t samples_count = 10;
for (uint32_t count = 0; (count < samples_count); ++count)
{
hello.index = count;
sprintf(hello.message, "HelloWorld %d", count);
printf("Sending: %d, msg: %s\n", hello.index, hello.message);
// 向 History 添加内容
CacheChange_t *change = writer->new_change([]() -> uint32_t
{ return 255; }, ALIVE);
if (!change) // In the case history is full, remove some old changes
{
std::cout << "cleaning history...";
writer->remove_older_changes(20);
change = writer->new_change([]() -> uint32_t
{ return 255; }, ALIVE);
}
change->serializedPayload.length = static_cast<uint32_t>(sizeof(HelloWorld));
memcpy(change->serializedPayload.data, &hello, sizeof(HelloWorld));
history->add_change(change);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
RTPSDomain::removeRTPSWriter(writer);
delete history;
RTPSDomain::removeRTPSParticipant(participant);
return 0;
}
RTPSSubscriber.cc
文件内容
#include <iostream>
#include <thread>
#include <chrono>
#include <signal.h>
#include <unistd.h>
#include <fastrtps/rtps/RTPSDomain.h>
#include <fastrtps/rtps/participant/RTPSParticipant.h>
#include <fastrtps/rtps/reader/ReaderListener.h>
#include <fastrtps/rtps/reader/RTPSReader.h>
#include <fastrtps/rtps/history/ReaderHistory.h>
#include <fastrtps/rtps/attributes/RTPSParticipantAttributes.h>
#include <fastrtps/rtps/attributes/ReaderAttributes.h>
#include <fastrtps/utils/fixed_size_string.hpp>
using namespace eprosima::fastrtps;
using namespace eprosima::fastrtps::rtps;
class HelloWorld
{
public:
int32_t index;
char message[100];
void set_index(int32_t _index)
{
index = _index;
}
int32_t get_index() const
{
return index;
}
void set_message(const char *_message)
{
strncpy(message, _message, sizeof(message) - 1);
message[sizeof(message) - 1] = '\0';
}
const char *get_message() const
{
return message;
}
};
class MyListener : public ReaderListener
{
public:
void onNewCacheChangeAdded(RTPSReader *reader, const CacheChange_t *const change) override
{
if (change->kind != ALIVE)
{
return;
}
++n_received;
HelloWorld hello;
memcpy(&hello, change->serializedPayload.data, sizeof(HelloWorld));
printf("Received: %d, msg: %s\n", hello.index, hello.message);
reader->getHistory()->remove_change((CacheChange_t *)change);
}
void onReaderMatched(
RTPSReader *,
MatchingInfo &info) override
{
if (info.status == MATCHED_MATCHING)
{
n_matched++;
}
}
uint32_t n_received;
uint32_t n_matched;
private:
using eprosima::fastrtps::rtps::ReaderListener::onReaderMatched;
} m_listener;
void signal_int_handler(int signum)
{
if (signum == SIGINT)
{
RTPSDomain::stopAll();
exit(0);
}
}
int main()
{
signal(SIGINT, signal_int_handler);
RTPSParticipantAttributes PParam;
PParam.builtin.discovery_config.discoveryProtocol = DiscoveryProtocol::SIMPLE;
PParam.builtin.use_WriterLivelinessProtocol = true;
PParam.participantID = 0;
PParam.setName("publisherParticipant");
RTPSParticipant *participant = RTPSDomain::createParticipant(0, PParam);
if (participant == nullptr)
{
std::cerr << "Error creating participant" << std::endl;
return -1;
}
ReaderAttributes RParam;
RParam.endpoint.topicKind = NO_KEY;
RParam.endpoint.reliabilityKind = RELIABLE;
RParam.endpoint.durabilityKind = TRANSIENT_LOCAL;
HistoryAttributes hatt;
hatt.payloadMaxSize = 255;
ReaderHistory *history = new ReaderHistory(hatt);
MyListener listener;
RTPSReader *reader = RTPSDomain::createRTPSReader(participant, RParam, history, &listener);
if (reader == nullptr)
{
std::cerr << "Error creating reader" << std::endl;
delete history;
// delete change_pool;
RTPSDomain::removeRTPSParticipant(participant);
return -1;
}
// topic
TopicAttributes Tatt;
Tatt.topicKind = NO_KEY;
Tatt.topicDataType = "string";
Tatt.topicName = "exampleTopic";
ReaderQos Rqos;
if (!participant->registerReader(reader, Tatt, Rqos))
{
std::cout << "ERROR: registerReader" << std::endl;
return -1;
}
std::cout << "Waiting for publications..." << std::endl;
printf("Press Enter to stop the Reader.\n");
std::cin.ignore();
RTPSDomain::removeRTPSReader(reader);
delete history;
// delete change_pool;
RTPSDomain::removeRTPSParticipant(participant);
return 0;
}
2.RTPS跨网段通信Demo
2.1.环境说明
VMware的Ubuntu22.04LTS虚拟机*3,已经可以跨网段通信,参考这篇跨网段通信的内容,其中网络IP仍是那片博客的内容
2.2.修改逻辑
在第一段程序的基础上进行添加
添加d_partcipantID
和d_domainID
的指定
Linux1(192.168.10.10)修改RTPSPublisher.cc
的RTPSParticipantAttributes
和创建逻辑部分为如下
const int d_domainID = 0;
const int d_partcipantID = 0;
RTPSParticipantAttributes PParam;
PParam.participantID = d_partcipantID;
Locator_t initial_peer;
IPLocator::setIPv4(initial_peer,"192.168.11.11");
initial_peer.port = 7412;
PParam.builtin.initialPeersList.push_back(initial_peer);
RTPSParticipant* participant = RTPSDomain::createParticipant(d_domainID,PParam);
Linux3(192.168.11.11)修改RTPSPublisher.cc
的RTPSParticipantAttributes
和创建逻辑部分为如下
const int d_domainID = 0;
const int d_partcipantID = 1;
RTPSParticipantAttributes PParam;
PParam.participantID = d_partcipantID;
Locator_t initial_peer;
IPLocator::setIPv4(initial_peer,"192.168.10.10");
initial_peer.port = 7410;
PParam.builtin.initialPeersList.push_back(initial_peer);
RTPSParticipant* participant = RTPSDomain::createParticipant(d_domainID,PParam);