C++发布订阅模式

article/2025/10/27 1:50:09

C++发布订阅模式

发布订阅模式主要包含三个部分:消息发布、消息订阅者、消息处理中心。与观察者模式相比多出了消息处理中心模块,这样在结构上可以解耦订阅者与发布者,功能上更加的丰富。

观察者模式

结构设计

  • 有一个消息list,主线程向这个list尾部追加消息,同时另一个子线程从消息list头部不断取出第一个消息
  • 查找消息订阅map,订阅者与消息设计为n:n关系,一个消息可被多个订阅者订阅,因此需依次执行订阅了这个消息的函数
    • 如:Stocks Trade消息需被Suber1和Suber4两个订阅者处理
fb39519ed7556a817daf14c5109f00f

简单来说就是:EventDeta*就是一个数据,并为之取了一个别名 * Trade,称之为消息。而很多类函数(普通函数也一样的)的正常执行需要这个EventData,因此这个类将其某个函数和EventData通过订阅列表绑定,当这个EventData也即是消息成为消息列表的第一个元素时,就执行和这个EventData绑定的所有类函数。

代码

[知乎]](https://zhuanlan.zhihu.com/p/484171260)

消息发布

EventData.h:

/* 定义有哪些消息、消息对应的数据当想要发自己的消息,可以在这里进行添加
*/
// 消息句柄用于标识消息
char * event_data1  = "Stocks Trade";  
// 消息主体,可做为订阅者的入参,以对此消息进行处理 
struct EventData1
{int e = 0;
};char * event_data2 = "Bonds Trade";
struct EventData2
{char * i = "receiver";
};char * event_data3 = "Funds Trade";
struct EventData3
{EventData1  data1;EventData2 data2;
};

消息订阅者

EventSuber.h: 订阅者消息处理函数一般接收两个参数,一个是自己的参数,一个是传过来的消息

这里可以不申明类,直接写三个消息处理函数也可以

#include"EventData.h"
#include<iostream>
#include<string>using namespace std;   // string是标准卡函数/*订阅消息处理模块 3个 */
class Subscriber1
{
public:// 利用静态成员函数,保留上一次消息处理结果// 不能访问非静态成员变量与非静态成员函数 => 传这个类的指针 void* _this// 强转_this指针,调用此类的成员函数、成员变量 _this=&(Subscriber1实例)// 给这个订阅器传一个消息static void HandleReceiveEvent (void * _this, void * data){Subscriber1 * th = (Subscriber1 *)_this;EventData1* d = (EventData1 *)data;  d->e += th->_data;printf("%s, data:%d \n",__FUNCTION__,d->e);}int _data = 10;
};class Subscriber2
{
public:static void HandleReceiveEvent (void * _this, void * data){Subscriber2 * th = (Subscriber2 *)_this;EventData2* d = (EventData2 *)data;string str(d->i);str += th->_data;printf("%s, data:%s \n",__FUNCTION__,str.c_str());}char * _data = " Subscriber 2";
};class Subscriber3
{
public:static void HandleReceiveEvent (void * _this, void * data){Subscriber3 * th = (Subscriber3 *)_this;EventData3* d = (EventData3 *)data;d->data1.e += th->_data1;string str(d->data2.i);str += th->_data2;printf("%s, data:%d , %s \n",__FUNCTION__,d->data1.e,str.c_str());}int _data1 = 30;char * _data2 = " Subscriber 3";
};

消息处理中心

EventDeal.h: 开启一个子线程,不断去消息list取消息,并将该消息将由所有订阅者进行处理

#include<pthread.h>
#include<map>
#include<list>
#include "EventSuber.h"/*消息处理中心*/
class EventMsgCentre
{
public:typedef  void(*HandleEvent)(void * , void *);/* 订阅消息节点 */// 消息处理函数设置两个入参:第一个_this指针是订阅器者,第二个是消息EventData// HandleReceiveEvent(suber,msg)形式struct EventSubscriberNode{void * _this;    // 只是普通的指针变量HandleEvent func;   // 函数指针,func无返回类型,且双入参都为void*};/* 发布消息节点 */struct EventPublishNode{char * event;void * data;};public:// 初始化消息发布、订阅锁EventMsgCentre(){pthread_mutex_init(&_mutexSubscriber, nullptr);pthread_mutex_init(&_mutexPublish, nullptr);}// 释放锁空间~EventMsgCentre(){pthread_mutex_destroy(&_mutexPublish);pthread_mutex_destroy(&_mutexSubscriber);}// 订阅消息 =》 订阅map追加元素void SubscriberEvent(char * event, EventSubscriberNode node){pthread_mutex_lock(&_mutexSubscriber);sMap[event].push_back(node);    pthread_mutex_unlock(&_mutexSubscriber);}// 删除消息订阅void releaseSubscriberEvent(char * event, EventSubscriberNode node){pthread_mutex_lock(&_mutexSubscriber);auto it = sMap.find(event);   // 订阅map中找到这个消息的订阅列表if(it != sMap.end()){for(auto ite = it->second.begin(); ite != it->second.end(); ){// 删除这个消息对应订阅列表的某个订阅if(ite->_this == node._this && ite->func == node.func){ite = it->second.erase(ite);}else{++ite;}}}pthread_mutex_unlock(&_mutexSubscriber);}// 发布消息,往消息列表添加一个消息节点void PublishEvent(char * event, void *data){EventPublishNode  node;node.event = event;node.data = data;pthread_mutex_lock(&_mutexPublish);plist.push_back(node);pthread_mutex_unlock(&_mutexPublish);}/* 消息处理中心  */static void *EventProcess(void * _this){EventMsgCentre * th = (EventMsgCentre *)_this;  while (1){EventPublishNode cur {nullptr, nullptr};  // 申明消息pthread_mutex_lock(&th->_mutexPublish);  // 消息发布锁// 取出第一个消息if (th->plist.empty()){pthread_mutex_unlock(&th->_mutexPublish);continue;}cur = th->plist.front();th->plist.pop_front();pthread_mutex_unlock(&th->_mutexPublish);// 消息订阅锁// 找到这个消息对应的订阅 pthread_mutex_lock(&th->_mutexSubscriber);auto it = th->sMap.find(cur.event);if(it != th->sMap.end()){for(auto ite = it->second.begin(); ite != it->second.end(); ++ite){ite->func(ite->_this, cur.data);   // 执行所有订阅者的消息动作}}pthread_mutex_unlock(&th->_mutexSubscriber);}}// 创建线程void theardProc(){/* 1.向调用者传递子线程的线程号 2.线程属性设置,一个结构体,包括线程优先级,线程栈大小3.指定子线程允许的函数,需要一个函数指针4.子线程运行的函数参数值*/int ret = pthread_create(&pt_id, nullptr, EventProcess, this);}public:pthread_mutex_t _mutexSubscriber;   // 消息订阅互斥锁pthread_mutex_t _mutexPublish;   // 消息发布互斥锁pthread_t pt_id;   // 线程idlist<EventPublishNode> plist;   // 消息发布列表map<char *, list<EventSubscriberNode>> sMap;   // 消息订阅map,一个订阅者可订阅多个消息
};

main线程

添加三个消息,并分别添加如顶图所示的订阅者(订阅者4换成订阅者1)

#include<iostream>
#include<map>
#include<list>
#include <windows.h>
#include <process.h>
#include"EventDeal.h"/* 测试。 */
int main()
{EventMsgCentre eveMsg; /* 初始化 */eveMsg.theardProc(); /* 启线程 */    /* 订阅消息 */Subscriber1  ber;ber._data = 100;     EventMsgCentre::EventSubscriberNode node1;// 节点的_this就是一个订阅器node1._this = &ber;      // 节点的函数指针就是订阅器的HandleReceiveEventnode1.func = Subscriber1::HandleReceiveEvent;  eveMsg.SubscriberEvent(event_data1, node1);  // 为消息1("Stocks Trade")添加一个订阅器1// 由于消息2和消息3不能直接转换成消息1,所以给消息1添加了一个重复的订阅者1eveMsg.SubscriberEvent(event_data1, node1);  Subscriber2 ber2;ber2._data = " Subscriber2_200";EventMsgCentre::EventSubscriberNode node2;node2._this = &ber2;node2.func = Subscriber2::HandleReceiveEvent;eveMsg.SubscriberEvent(event_data2, node2);Subscriber3 ber3;ber3._data1 = 300;ber3._data2 = " Subscriber3_300";EventMsgCentre::EventSubscriberNode node3;node3._this = &ber3;node3.func = Subscriber3::HandleReceiveEvent;eveMsg.SubscriberEvent(event_data3, node3);/* 发布消息 */EventData1 d1{1};eveMsg.PublishEvent(event_data1, &d1);EventData2 d2 {"event_data2"};eveMsg.PublishEvent(event_data2, &d2);EventData3 d3 {3,"event_data3"};eveMsg.PublishEvent(event_data3, &d3);// Sleep(1000);    // 休眠一秒,主线程退出,子线程消息处理函数随之退出// eveMsg.~EventMsgCentre();while(1){}     // 等待EventProcess执行完成return 0;
}
20220927223828

http://chatgpt.dhexx.cn/article/7GCteBef.shtml

相关文章

Java实现发布订阅模式

什么是发布订阅模式 发布订阅模式是软件开发者很常见的一种设计模式&#xff0c;很多开源库都使用了发布订阅模式&#xff0c;例如RxJava、EventBus、Vue等&#xff0c;所以学习该模式还是很有必要的。 该模式中存在一个或多个发布者&#xff0c;一个或多个订阅者&#xff0c…

设计模式 —— 发布订阅模式

设计模式 —— 发布订阅模式 《工欲善其事&#xff0c;必先利其器》 我在之前有写过一篇关于 《观察者模式》 的文章&#xff0c;大家有兴趣的可以去看看&#xff0c;个人认为那个例子还是挺生动的。&#xff08;狗头&#xff09; 不过今天我们要学习的是&#xff0c;发布订阅…

小侃设计模式(十八)-发布订阅模式

1.概述 发布订阅模式又叫观察者模式&#xff08;Observer Pattern&#xff09;&#xff0c;它是指对象之间一对多的依赖关系&#xff0c;每当那个特定对象改变状态时&#xff0c;所有依赖于它的对象都会得到通知并被自动更新&#xff0c;它是行为型模式的一种。观察者模式内部…

发布-订阅模式

发布-订阅模式 学习知识要善于思考&#xff0c;思考&#xff0c;再思考。 —— 爱因斯在众多设计模式中&#xff0c;可能最常见、最有名的就是发布 - 订阅模式了&#xff0c;本篇我们一起来学习这个模式。 发布 - 订阅模式 &#xff08;Publish-Subscribe Pattern, pub-sub&a…

什么是发布订阅模式?

发布-订阅模式&#xff08;Publish-Subscribe pattern&#xff09;是一种软件架构模式&#xff0c;用于实现组件之间的解耦和消息传递。在这种模式中&#xff0c;组件&#xff08;发布者&#xff09;将消息发送到一个中心&#xff08;消息代理或主题&#xff09;&#xff0c;然…

发布订阅模式

零、目录 应用场景实现原理代码实现全局模式下的订阅发布模式&#xff08;泛化的订阅发布模式&#xff09;总结 一、应用场景 ​ 发布订阅模式&#xff0c;广泛的存在于在我们的生活之中。 ​ 举个一个简单的例子来说&#xff0c;当我们在浏览视频或者博客论坛之类的网…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:VLCPlayer

本文简述如何在Smobiler中使用VLCPlayer插件&#xff0c;该插件支持播放rtsp流。 Step 1. 新建一个SmobilerForm窗体&#xff0c;再拖入VLCPlay&#xff0c;布局如下 在设计器中给VLCPlayer.Url赋值或者在窗体的Load事件中赋值 演示使用的rtsp流地址 rtsp://wowzaec2demo.strea…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:TTS

本文简述如何在Smobiler中使用TTS文字转语音。 Step 1. 新建一个SmobilerForm窗体&#xff0c;并在窗体中加入TTS和Button&#xff0c;布局如下 Button的点击事件代码&#xff1a; private void button1_Press(object sender, EventArgs e){ //第一个参数为文本&#xff1b;第…

Smobiler 仿得到APP个人主页

原型如下&#xff1a; 完整代码参考 https://github.com/comsmobiler/BlogsCode/blob/master/Source/BlogsCode_SmobilerForm/MyForm/dedao.cs 思路 可以将原型按照上图分成2个部分&#xff0c;部分A可以使用label、image、button、imagebutton、fontIcon控件来实现&#xff…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:PDFView

本文简述如何在Smobiler中使用PDFView。 Step 1. 新建一个SmobilerForm窗体&#xff0c;再拖入PDfView&#xff0c;布局如下 PDFView.ResourcrPath默认Document&#xff0c;指项目下\Resources\Document&#xff0c;若是pdf文件放在该文件夹下&#xff0c;则在设计器中直接赋值…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:OCR组件

本文简述如何在Smobiler中使用OCR组件进行文字识别。 Step 1. 新建一个SmobilerForm窗体&#xff0c;并在窗体中加入OCR和Button&#xff0c;布局如下 Button的点击事件代码&#xff1a; private void button1_Press(object sender, EventArgs e){ocr1.Recognize((obj,args)>…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:ArcFace人脸识别

本文简述如何在Smobiler中使用ArcFace&#xff08;虹软人脸识别&#xff09;。 Step 1. 新建一个SmobilerForm窗体&#xff0c;再拖入Button,Label,TextBox和AcrFace,布局如下 在设计器中给MediaView.Url赋值或者在窗体的Load事件中赋值 Button的事件代码如下 string message …

移动OA办公——Smobiler第一个开源应用解决方案,快来get吧

产品简介 SmoONE是一款移动OA类的开源解决方案&#xff0c;通过Smobiler平台开发&#xff0c;包含了注册、登陆、用户信息等基本功能。集成了OA中使用场景较多的报销、请假、部门管理、成本中心等核心功能。 免费获取方案 开源代码&#xff1a;https://github.com/comsmobile…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:UsbSerial串口通讯组件

本文简述如何在Smobiler中使用UsbSerial。 Step 1. 新建一个SmobilerForm窗体&#xff0c;再拖入UsbSerial和Button&#xff0c;布局如下 按钮事件代码&#xff1a; //连接private void button1_Press_2(object sender, EventArgs e){usbSerial1.Connect(Smobiler.Plugins.USBS…

Smobiler实现手机弹窗

前言 在实际项目中有很多场景需要用到弹窗&#xff0c;如图1 那么这些弹窗在Smobiler中如何实现呢&#xff1f; 正文 Smobiler实现弹窗有两种方式&#xff1a;1.MessageBox.Show 2.ShowDialog和ShowContextDialog。前者适合简易弹窗&#xff0c;后者适合自定义弹窗。 Messa…

Smobiler实现美观登录界面——C# 或.NET Smobiler实例开发手机app(二)

目录 一、 本文目标 二、 准备工作 1、 数据库 2、 材料 三、 界面布局 1、设置控件的属性值 &#xff08;1&#xff09; 输入框 &#xff08;2&#xff09; 图片属性 &#xff08;3&#xff09; HandElectricity的标题的label属性 &#xff08;4&#xff09;登录按钮…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:MediaView

本文简述如何在Smobiler中使用MediaView。 Step 1. 新建一个SmobilerForm窗体&#xff0c;再拖入MediaView&#xff0c;MediaView.Size设置&#xff08;300,225&#xff09;,布局如下 在设计器中给MediaView.Url赋值或者在窗体的Load事件中赋值 播放本地视频可以通过GetResourc…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:AliPay组件

本文简述如何在Smobiler中调用支付宝支付。 Step 1. 界面 新建一个窗体&#xff0c;并在窗体中拖入Button&#xff0c;Label&#xff0c;AliPay等控件&#xff0c;布局如下&#xff1a; Step 2. 代码 在窗体中声明变量 //订单编号private string tradeNo;//支付宝应用编号&am…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:BarcodeReader组件

本文简述如何在Smobiler中使用BarcodeReader组件进行条码识别。Barcodereader通过机器学习能识别不规则条码&#xff0c;效率更好。 Step 1. 新建一个SmobilerForm窗体&#xff0c;并在窗体中加入Barcodereader和Button&#xff0c;布局如下 Button的点击事件代码&#xff1a; …

.NET(C#、VB)APP开发——Smobiler平台控件介绍:LiveStream和LiveStreamPlayer

本文简述如何在Smobiler中使用LiveStream和LiveStreamPlayer。 LiveStream 直播推送插件 Step 1. 新建一个SmobilerForm窗体&#xff0c;并在窗体中加入LiveStream和Button&#xff0c;布局如下 选中LisvStream&#xff0c;在设计器中设置Url&#xff08;需要事先准备一个视频…