第一章:FastDDSclass="superseo">学习笔记之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()
{
/*1. Init m_Hello */
// 设置初始的index = 0
m_Hello.index(0);
// 设置初始值message
m_Hello.message("HelloWorld");
/*2. 初始化参与者的参数对象 */
// 创建参与者的相关属性
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");
/*3. 创建participant */
mp_participant = Domain::createParticipant(PParam);
if (mp_participant == nullptr)
{
return false;
}
;
/*4. 注册数据类型 */
Domain::registerType(mp_participant, &m_type);
/*5. 初始化writer */
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;
/*6. 创建publisher */
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)
{
/* 这里samples传入值为10 */
if (samples == 0)
{
while (!stop)
{
/* 执行函数publish */
if (publish(false))
{
std::cout << "Message: " << m_Hello.message() << " with index: " << m_Hello.index() << " SENT" <<std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
}
}
else
{
for (uint32_t i = 0; i < samples; ++i)
{
if (!publish())
{
--i;
}
else
{
std::cout << "Message: " << m_Hello.message() << " with index: " << m_Hello.index() << " SENT" <<std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
}
}
}
进入线程后主要是去执行了publish
函数:
bool HelloWorldPublisher::publish(bool waitForListener)
{
/* waitForListener传入为false */
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()
{
/*1. 初始化参与者的参数对象 */
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");
/*2. 创建参与者 */
mp_participant = Domain::createParticipant(PParam);
if (mp_participant == nullptr)
{
return false;
}
/*3. 注册数据类型 */
Domain::registerType(mp_participant, &m_type);
/*4. 初始化reader */
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;
/*4. 创建subscriber */
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 当订阅者与新的 Writer 匹配(或不匹配)时调用的方法。
*/
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
).
其中定义了``函数:
/**
* @brief 从订阅者处获取下一个样本。 从订户中删除样本。
* @param sample 指向您希望存储样本的对象的指针。
* @param info 指向 SampleInfo_t 结构的指针,该结构通知您有关您的样本。
* @return 如果取样则为真。
* @note 该方法被阻塞一段时间。
* SubscriberAttributes 上的 ReliabilityQosPolicy.max_blocking_time 定义了这段时间。
*/
bool takeNextData(void* sample,SampleInfo_t* info);
好了今天的分享就到这里,明天继续。_
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)