第一章:FastDDS学习笔记之Ubuntu22上安装fastDDS环境
第二章:FastDDS学习笔记之HelloWorld示例程序编译和运行
第三章:FastDDS学习笔记之Fast-DDS-Gen安装记录
代码位置:~/Fast-DDS/Fast-DDS/examples/C++/HelloWorldExample
设置相应的环境变量因为上一章节中,我们编译的库并没有放到系统中,而是放在了一个文件夹中,所以这里我们要先设定以下对应的环境变量:
export LD_LIBRARY_PATH=/home/xiaoqing/Fast-DDS/install/lib
这里的路径就是编译时候,填写的路径。
如果想要永久生效,执行以下命令:
echo 'export LD_LIBRARY_PATH=/home/xiaoqing/Fast-DDS/install/lib' >> ~/.bashrc
这里我是用的是永久生效的。
编译HelloWorld在ubuntu中执行以下命令:
mkdir build && cd build cmake .. -DCMAKE_PREFIX_PATH=~/Fast-DDS/install/
运行效果:
继续编译:
make
生成运行实例:
接下来我们来运行HelloWorld,看一下效果。
新开一个终端,运行一个publisher:
./HelloWorldExample publisher
再新开一个终端,运行一个subscriber:
./HelloWorldExample subscriber
执行效果:
publisher:
subscriber:
源码的文件目录:
HelloWorldExample ├── build │ ├── CMakeCache.txt │ ├── CMakeFiles │ ├── cmake_install.cmake │ ├── HelloWorldExample │ └── Makefile ├── CMakeLists.txt ├── HelloWorld.cxx // 由idl生成 ├── HelloWorld.h // 由idl生成 ├── HelloWorld.idl //idl 文件 ├── HelloWorld_main.cpp ├── HelloWorldPublisher.cpp ├── HelloWorldPublisher.h ├── HelloWorldPubSubTypes.cxx // 由idl生成 ├── HelloWorldPubSubTypes.h // 由idl生成 ├── HelloWorldSubscriber.cpp ├── HelloWorldSubscriber.h └── README.txt
代码中
HelloWorld.idl 定义payload,会生成HelloWorld.h,HelloWorld.cxx,HelloWorldPubSubTypes.h与HelloWorldPubSubTypes.cxx四个文件,包含了序列化与反序列化的逻辑。
先来看一下HelloWorld.idl:
struct HelloWorld
{
unsigned long index;
string message;
};
只是简单地定义了下数据传输的结构体。
结构体包含一个id和一个字符串类型的message。
而这,也就是DDS概念模型中,TopicDataType的定义。
主题在概念上适合发布和订阅。
从OMG的DDS官网我们可以知道,IDL(DDS基于的是IDL4 v4.2标准)是一种不依赖于编程语言的,用于定义数据类型和接口的描述性语言。他同时也是OMG组织制定的标准。Fast DDS-Gen作为一个Java应用程序,就是用来解析idl文件,生成数据类型定义的(即HelloWorld.h,HelloWorld.cxx,HelloWorldPubSubTypes.h,HelloWorldPubSubTypes.cxx)。
生成的文件HelloWorld.h,HelloWorld.cxx`中会根据这个结构体生成类,并添加一些序列化使用的函数:
class HelloWorld
{
public:
eProsima_user_DllExport HelloWorld& operator =(
const HelloWorld& x);
eProsima_user_DllExport HelloWorld& operator =(
HelloWorld&& x);
eProsima_user_DllExport bool operator ==(
const HelloWorld& x) const;
eProsima_user_DllExport bool operator !=(
const HelloWorld& x) const;
eProsima_user_DllExport void index(
uint32_t _index);
eProsima_user_DllExport uint32_t index() const;
eProsima_user_DllExport uint32_t& index();
eProsima_user_DllExport void message(
const std::string& _message);
eProsima_user_DllExport void message(
std::string&& _message);
eProsima_user_DllExport const std::string& message() const;
eProsima_user_DllExport std::string& message();
eProsima_user_DllExport static size_t getMaxCdrSerializedSize(
size_t current_alignment = 0);
eProsima_user_DllExport static size_t getCdrSerializedSize(
const HelloWorld& data,
size_t current_alignment = 0);
eProsima_user_DllExport void deserialize(
eprosima::fastcdr::Cdr& cdr);
eProsima_user_DllExport static size_t getKeyMaxCdrSerializedSize(
size_t current_alignment = 0);
eProsima_user_DllExport static bool isKeyDefined();
eProsima_user_DllExport void serializeKey(
eprosima::fastcdr::Cdr& cdr) const;
private:
uint32_t m_index;
std::string m_message;
};
在main函数中主要是根据参数创建相应的对象:
int main(int argc, char** argv)
{
std::cout << "Starting "<< std::endl;
int type = 1;
int count = 10;
long sleep = 100;
if(argc > 1)
{
if(strcmp(argv[1],"publisher")==0)
{
type = 1;
if (argc >= 3)
{
count = atoi(argv[2]);
if (argc == 4)
{
sleep = atoi(argv[3]);
}
}
}
else if(strcmp(argv[1],"subscriber")==0)
type = 2;
}
else
{
std::cout << "publisher OR subscriber argument needed" << std::endl;
Log::Reset();
return 0;
}
switch(type)
{
case 1:
{
//创建publisher对象
HelloWorldPublisher mypub;
//初始化
if(mypub.init())
{
//运行publisher,发布10次
mypub.run(count, sleep);
}
break;
}
case 2:
{
//创建subscriber对象
HelloWorldSubscriber mysub;
//初始化
if(mysub.init())
{
//运行subscriber
mysub.run();
}
break;
}
}
Domain::stopAll();
Log::Reset();
return 0;
}
真正的逻辑实现放在了HelloWorldPublisher和HelloWorldSubscriber的实现中。
来看一下初始化部分。
HelloWorldPublisher流程分析HelloWorldPublisherc初始化基本步骤:
- 初始化m_Hello的内容,m_Hello即为需要发送的data
- 初始化参与者的参数对象
- 创建participant
- 初始化writer
- 创建publisher
代码分析如下:
publisher
bool HelloWorldPublisher::init()
{
// 设置初始的index = 0
m_Hello.index(0);
// 设置初始值message
m_Hello.message("HelloWorld");
// 创建参与者的相关属性
ParticipantAttributes PParam;
PParam.rtps.builtin.discovery_config.discoveryProtocol = DiscoveryProtocol_t::SIMPLE;
PParam.rtps.builtin.discovery_config.use_SIMPLE_EndpointDiscoveryProtocol = true;
PParam.rtps.builtin.discovery_config.m_simpleEDP.use_PublicationReaderANDSubscriptionWriter = true;
PParam.rtps.builtin.discovery_config.m_simpleEDP.use_PublicationWriterANDSubscriptionReader = true;
PParam.rtps.builtin.discovery_config.leaseDuration = c_TimeInfinite;
PParam.rtps.setName("Participant_pub");
mp_participant = Domain::createParticipant(PParam);
if (mp_participant == nullptr)
{
return false;
}
;
Domain::registerType(mp_participant, &m_type);
PublisherAttributes Wparam;
Wparam.topic.topicKind = NO_KEY;
Wparam.topic.topicDataType = "HelloWorld";
Wparam.topic.topicName = "HelloWorldTopic";
Wparam.topic.historyQos.kind = KEEP_LAST_HISTORY_QOS;
Wparam.topic.historyQos.depth = 30;
Wparam.topic.resourceLimitsQos.max_samples = 50;
Wparam.topic.resourceLimitsQos.allocated_samples = 20;
Wparam.times.heartbeatPeriod.seconds = 2;
Wparam.times.heartbeatPeriod.nanosec = 200 * 1000 * 1000;
Wparam.qos.m_reliability.kind = RELIABLE_RELIABILITY_QOS;
mp_publisher = Domain::createPublisher(mp_participant, Wparam, (PublisherListener*)&m_listener);
if (mp_publisher == nullptr)
{
return false;
}
return true;
}
其中m_Hello类型维HelloWorld, 调用index是设置其值:
void HelloWorld::index(uint32_t _index)
{
m_index = _index;
}
然后开始执行其run函数:
void HelloWorldPublisher::run(uint32_t samples,uint32_t sleep)
{
stop = false;
std::thread thread(&HelloWorldPublisher::runThread, this, samples, sleep);
if (samples == 0)
{
std::cout << "Publisher running. Please press enter to stop the Publisher at any time." << std::endl;
std::cin.ignore();
stop = true;
}
else
{
std::cout << "Publisher running " << samples << " samples." << std::endl;
}
thread.join();
}
可以看出run函数中只是启动了个线程,接着便进入线程中:
void HelloWorldPublisher::runThread(uint32_t samples,uint32_t sleep)
{
if (samples == 0)
{
while (!stop)
{
if (publish(false))
{
std::cout << "Message: " << m_Hello.message() << " with index: " << m_Hello.index() << " SENT" <
for (uint32_t i = 0; i < samples; ++i)
{
if (!publish())
{
--i;
}
else
{
std::cout << "Message: " << m_Hello.message() << " with index: " << m_Hello.index() << " SENT" <
进入线程后主要是去执行了publish函数:
bool HelloWorldPublisher::publish(bool waitForListener)
{
if (m_listener.firstConnected || !waitForListener || m_listener.n_matched > 0)
{
// 设定index的值,index初始化为0
m_Hello.index(m_Hello.index() + 1);
// 发布数据m_Hello
mp_publisher->write((void*)&m_Hello);
return true;
}
return false;
}
其中mp_publisher的类型是eprosima::fastrtps::Publisher.(头文件路径:Fast-DDS/include/fastrtps/publisher/Publisher.h)
write函数实现部分在代码:Fast-DDS/src/cpp/fastrtps_deprecated/publisher/Publisher.cpp
bool Publisher::write(void* Data)
{
logInfo(PUBLISHER, "Writing new data");
return mp_impl->create_new_change(ALIVE, Data);
}
HelloWorldsubscriber流程分析
HelloWorldSubscriber初始化基本步骤:
- 初始化参与者的参数对象
- 创建participant
- 初始化reader
- 创建publisher
代码分析如下
bool HelloWorldSubscriber::init()
{
ParticipantAttributes PParam;
PParam.rtps.builtin.discovery_config.discoveryProtocol = DiscoveryProtocol_t::SIMPLE;
PParam.rtps.builtin.discovery_config.use_SIMPLE_EndpointDiscoveryProtocol = true;
PParam.rtps.builtin.discovery_config.m_simpleEDP.use_PublicationReaderANDSubscriptionWriter = true;
PParam.rtps.builtin.discovery_config.m_simpleEDP.use_PublicationWriterANDSubscriptionReader = true;
PParam.rtps.builtin.discovery_config.leaseDuration = c_TimeInfinite;
PParam.rtps.setName("Participant_sub");
mp_participant = Domain::createParticipant(PParam);
if (mp_participant == nullptr)
{
return false;
}
Domain::registerType(mp_participant, &m_type);
SubscriberAttributes Rparam;
Rparam.topic.topicKind = NO_KEY;
Rparam.topic.topicDataType = "HelloWorld";
Rparam.topic.topicName = "HelloWorldTopic";
Rparam.topic.historyQos.kind = KEEP_LAST_HISTORY_QOS;
Rparam.topic.historyQos.depth = 30;
Rparam.topic.resourceLimitsQos.max_samples = 50;
Rparam.topic.resourceLimitsQos.allocated_samples = 20;
Rparam.qos.m_reliability.kind = RELIABLE_RELIABILITY_QOS;
Rparam.qos.m_durability.kind = TRANSIENT_LOCAL_DURABILITY_QOS;
mp_subscriber = Domain::createSubscriber(mp_participant, Rparam, (SubscriberListener*)&m_listener);
if (mp_subscriber == nullptr)
{
return false;
}
return true;
}
继续看一下run函数:
void HelloWorldSubscriber::run()
{
std::cout << "Subscriber running. Please press enter to stop the Subscriber" << std::endl;
std::cin.ignore();
}
可以看出HelloWorldSubscriber的run函数中几乎什么都没做,只是等待一个按键后退出。
这里的主要读取数据的操作是由init函数中注册的&m_listener来完成的。
m_listener的相关代码:
class SubListener : public eprosima::fastrtps::SubscriberListener
{
public:
SubListener(): n_matched(0), n_samples(0)
{
}
~SubListener()
{
}
void onSubscriptionMatched(eprosima::fastrtps::Subscriber* sub, eprosima::fastrtps::rtps::MatchingInfo& info);
void onNewDataMessage(eprosima::fastrtps::Subscriber* sub);
HelloWorld m_Hello;
eprosima::fastrtps::SampleInfo_t m_info;
int n_matched;
uint32_t n_samples;
} m_listener;
SubscriberListener的代码路径是Fast-DDS/include/fastrtps/subscriber/SubscriberListener.h
class RTPS_DllAPI SubscriberListener
{
public:
SubscriberListener(){}
virtual ~SubscriberListener(){}
virtual void onNewDataMessage(Subscriber* sub)
{
(void)sub;
}
virtual void onSubscriptionMatched(Subscriber* sub,rtps::MatchingInfo& info)
{
(void)sub;
(void)info;
}
virtual void on_requested_deadline_missed(Subscriber* sub,const RequestedDeadlineMissedStatus& status)
{
(void)sub;
(void)status;
}
virtual void on_liveliness_changed(Subscriber* sub,const LivelinessChangedStatus& status){
(void)sub;
(void)status;
}
};
可以看出onNewDataMessage函数是消息相应的函数:
void HelloWorldSubscriber::SubListener::onNewDataMessage(Subscriber* sub)
{
if (sub->takeNextData((void*)&m_Hello, &m_info))
{
if (m_info.sampleKind == ALIVE)
{
this->n_samples++;
// Print your structure data here.
std::cout << "Message " << m_Hello.message() << " " << m_Hello.index() << " RECEIVED" << std::endl;
}
}
}
这里的实现就是获取数据后并打印。
sub的类型为eprosima::fastrtps::Subscriber*(头文件路径:Fast-DDS/include/fastrtps/subscriber/Subscriber.h).
其中定义了``函数:
bool takeNextData(void* sample,SampleInfo_t* info);
好了今天的分享就到这里,明天继续。_



