VS C++ 实现发布订阅模式(Publish/Subscribe)——基于旧版PX4 uORB模式

源码已经上传至我的 [Gitee](https://gitee.com/ascloudwalker),可用如下代码下载:
git clone https://gitee.com/ascloudwalker/c-publish--subscribe.git

- 前言

    最近接触到一些工程上的代码,使用全局变量或者函数指针的方式进行数据传递,在某些时候不是很方便:

  • 通过函数一层一层传递数据很麻烦;
  • 修改全局变量不可控;

此时,我想起了PX4的uORB(Micro Object Request Broker,微对象请求代理器)。

- 原理

    uORB本质上是一种发布订阅模式,什么是发布订阅模式?发布订阅模式:假设存在老师T(teacher),黑板B(blackboard),同学S(student).老师T每隔一个时间t1就去黑板B上发布一则通知,学生S每隔一段时间就去看(订阅)黑板上以下老师发布了什么消息。当然,这是最简单的模式,实际应用中实现还有很多附加功能,比如说:实际实现中,学生并不会先去看黑板上的内容,他可能通过某种机制先检查黑板上的内容是否更新,如果没更新就没有必要去看,如果更新了就去看看写了什么。又比如多个老师都可以在黑板上发布消息,也可以有多个同学去查看黑板发布了什么,当然也会有多块黑板。
在这里插入图片描述
    以上是一种通俗的说法,通俗的优点是好理解,缺点是显得Low。如果想要显得更加专业,请参考以下文章。

    再好的想法,只有实现了才能发挥作用,我开始查找现成的源码。

  • 首先,PX4的uORB、ROS等成熟的代码中都有该功能,优点是代码完善、功能齐全、结构完备;缺点是繁杂,不适合初学者理解,就算是看明白了也记不住;
  • 其次,网上一些单独的所谓发布订阅模式实现,与我所想的有些差别。

我决定,将uORB中最基本的架构抽离出来,使用Visual Studio C++实现。首先列出参考资源以示尊敬:

- 实现

干饭人干饭魂,干完代码干工程,我想实现最简单的功能,构建四个线程:

1.  主线程:1000ms/1HZ,打印主线程运行次数; 
2.  线程:50ms/20HZ,发布一次更新数据;
3.  线程1:100ms/10HZ,订阅数据并打印;
4.  线程2:100ms/10HZ,订阅数据并打印

    实现方法,当前的PX4版本,uORB的封装更好,代码结构更加简洁,但是不便于提取结构,本文主要参考了zarathustr /uORB提取出来的一个可移植版本,并在其基础上构建了Visual C++工程:

> step1: 构建黑板

// uORB.h
/// 这相当于定义黑板的基本结构
struct Data {
	void	*data = nullptr;
	bool	published = false;
};

extern Data *topic_data[1];
// pubsub.cpp
/// 实例化黑板,需要发布多少个主题(topic),就需要实例化多少黑板
const int total_uorb_num = 1;
Data *topic_data[total_uorb_num];
// pubsub.cpp
/// 对象请求代理器初始化,主要是因为结构体指针必须初始化,可以通过断点运行查看该步骤的作用
void orbInit(void)
{
	for (int i = 0; i < total_uorb_num; ++i)
	{
		topic_data[i] = new Data;
	}
}

> step2: 构建主题(topic),相当于准备好需要在黑板上发布数据的表格形式

// sample_topic.h
#ifdef __cplusplus
struct sample_topic_s {
#else
struct sample_topic_s {
#endif
	time_t timestamp;  // 时间戳,用于日志
	float  rollBody;
	float  pitchBody;
	float  yawBody;
	int  q;
#ifdef __cplusplus

#endif
};

> step3: 在黑板上构建公告板,相当于在黑板上画好表格。

// pubsub.cpp
/// 公告
advert_t Advertise(const void *data, int len)
{
	advert_t advert = nullptr;
	if (topic_data[0][0].data == nullptr)
	{
		topic_data[0][0].data = new int[len]; // 根据需要发布的数据data长度和数据类型,分配内存空间
		// 相当于根据需要发布的数据长度,在黑板上画好表格,发布的时候就只更新表格中的数据
	}
	
	std::memcpy(topic_data[0][0].data, data, len);
	advert = (void*)(&topic_data[0][0]);

	return advert;
}

> step4: 发布主题

// pubsub.cpp
/// 发布
void Publisher::Publish(advert_t handle, const void *data, int len)
{
	std::memcpy(topic_data[0][0].data, data, len); // 将数据拷贝到黑板的表格中
}

> step5: 订阅主题

// pubsub.cpp
/// 订阅
void Subscriber::Subscribe(void *buffer)
{
	memcpy(buffer, topic_data[0][0].data, sizeof(*(sample_topic_s *)topic_data[0][0].data));
}
// 将黑板上的数据拷贝到本地缓存使用

通过以上过程,构建了发布订阅的基本流程
在这里插入图片描述

> step5 应用

/*
uORB.cpp : 定义控制台应用程序的入口点。
Author:as.ck
Date:2021-01-20
Function: 模拟PX4的uORB的发布订阅模式
Copyright(C):2021, All Rights Reserved.
*/


#include "stdafx.h"
#include <iostream>
#include <vector>
#include <windows.h>
#include <iostream>
#include <time.h>  
#include "sample_topic_s.h"
#include "pubsub.h"


using namespace std;

/// 创建发布器和订阅器
Publisher  *pub; 
Subscriber *sub;

advert_t  sample_adv;


/// 线程函数0-主题更新发布线程
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	sample_topic_s	sample_topic_user = {};

	/// 数据更新
	sample_topic_user.pitchBody = 1.0;
	sample_topic_user.rollBody  = 2.0;
	sample_topic_user.yawBody   = 3.0;
	sample_topic_user.timestamp = time(NULL);

	//int a = sizeof(sample_topic_user);

	/// 公布主题
	sample_adv = Advertise(&sample_topic_user,sizeof(sample_topic_user));
	
	while(1)
	{
		/// 数据更新
		sample_topic_user.pitchBody++;

		/// 向主题发布数据		
		pub->Publish(sample_adv, &sample_topic_user, sizeof(sample_topic_user));
		cout <<"  " << "数据更新" << endl;

		Sleep(50);
	}

	return 0L;
}

/// 线程函数1-主题订阅线程
sample_topic_s	sample_topic_sub;
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
	while (1)
	{ 	
		sub->Subscribe(&sample_topic_sub);
		cout<< "   " << "线程1:"<<sample_topic_sub.pitchBody << endl;
		Sleep(100);
	}

	return 0L;
}

/// 线程函数2-主题订阅线程
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{

	while (1)
	{
		sub->Subscribe(&sample_topic_sub);
		cout<< "   " << "线程2:" << sample_topic_sub.pitchBody << endl;
		Sleep(100);
	}
	return 0L;
}

/// 主函数入口
int main()
{
	/// 初始化发布订阅模式
	orbInit();

	/// 创建三个线程
	HANDLE thread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
	HANDLE thread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
	HANDLE thread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);

	/// 关闭线程
	CloseHandle(thread);
	CloseHandle(thread1);
	CloseHandle(thread2);

	/// 主线程的执行路径
	int i = 0;
	while (1)
	{
		cout << "主线程:i = " << i << endl;
		Sleep(1000);
		i++;
	}

	return 0;
}

/*--------------End of File----------------*/

运行结果:

-后记

    以上是基本的发布订阅模式实现,实际PX4 uORB功能更加齐全,比如多个主题、更新检查、优先等级等,值得借鉴。
    需要注意的是,在本文中,我订阅的时候是直接将数据拷贝回来了,有些不合理。订阅应当返回黑板(公告板)的地址,在需要用数据的时候,根据黑板地址检查更新,再将数据拷贝回本地使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值