概述
- 本文主要描述一个超精简的订阅发布事件组件--SPEvent。
- 在实际开发过程中,一个事件的产生会产生很多业务的执行,或者多个事件都要执行同一个业务的执行。在这种场景下有两种做法:
- 将同一个事件的业务放在一个函数中,然后事件产生的时候执行对应的函数。
- 某个业务需要哪个事件,它自己监听对应事件并执行。
- 显然,第一种策略会将业务与业务之间耦合在一起,对后期维护是非常痛苦的;第二种显然会更加有优势,不同业务完全解耦,独立完成事件的业务。
- 第二种策略的方式,实际在软件架构中经常看到,比如MQTT的通信(通过订阅对应的topic去监听对应内容)。
- 有了上述的需求,作者做了一个超精简的订阅发布事件组件。整个逻辑很简单。
超精简的SPEvent组件,实现方法
-
整个订阅发布事件机制,引入两个东西:EventHub和EventNode。
- EventHub:每一个事件类型都为一个EventHub,然后挂在HubList中。
- EventNode:每一个订阅事件的业务为一个EventNode,然后挂在对应的EventHub中。
-
整个订阅发布事件机制围绕着EventHub和EventNode,特点:
- 资源占用极小,接口操作简单
- 事件支持动态订阅,动态注销。
-
SPEvent采用双向链表进行维护整个订阅-发布逻辑
- SPEvent一定存在一个EventHubList链表来维护事件类型,它默认是没有任何EventHub节点,
- 订阅事件流程:当订阅者订阅事件之后,如果事件不存在,则申请一个EventHub,并将EventHub挂在到EventHubList链表中;然后申请一个EventNode,及将对应EventNode挂在EventNodeList链表。
- 发布事件流程:当发布者发布事件时,会从EventHubList中查询有没有对应的EventHub,如果EventHub存在,则将事件消息发布给对应EventHub下所有EventNode。
- 注销事件订阅流程:当订阅者注销已经订阅的事件,会从EventHubList中查询有没有对应的EventHub,如果EventHub存在,则将对应EventNode从EventHub中删除。
超精简的SPEvent组件,接口说明:
函数 | 说明 |
---|---|
SPEventInit | 初始化函数 |
SPEventDeinit | 去初始化函数 |
SPEventSubscribe | 订阅事件函数 |
SPEventUnsubscribe | 注销订阅事件函数 |
SPEventPublish | 发布事件消息 |
SPEventClear | 清除事件池 |
RecvtInfoDump | 导出事件池信息 |
超精简的SPEvent组件,代码实现
- 整个代码接口存在3个文件:spevent.c、spevent.h、spevent_def.h。其中:
- spevent_def.h文件说明:定义了屏蔽平台相关接口的宏和定义了双向量表操作的宏定义。双向量表在SPEvent中式至关重要数据结构:
#ifndef__SPEVENT_DEF_H__
#define__SPEVENT_DEF_H__
#include
#include
#include
#include
#include
#include
#include
#defineSPEVENT_INLINEstatic__inline
#defineSPEVENT_EVENT_NAME_LEN16
#ifndefSPEVENT_MALLOC
#defineSPEVENT_MALLOCmalloc
#endif
#ifndefSPEVENT_FREE
#defineSPEVENT_FREEfree
#endif
#ifndefSPEVENT_PRINT
#defineSPEVENT_PRINTprintf
#endif
structSPEventListNode
{
structSPEventListNode*next;
structSPEventListNode*prev;
};
typedefstructSPEventListNodeSPEventList;
SPEVENT_INLINEvoidSPEVENT_LIST_INIT(SPEventList*l)
{
l->next=l->prev=l;
}
SPEVENT_INLINEvoidSPEVENT_LIST_INSERT_AFTER(SPEventList*l,SPEventList*n)
{
l->next->prev=n;
n->next=l->next;
l->next=n;
n->prev=l;
}
SPEVENT_INLINEvoidSPEVENT_LIST_INSERT_BEFORE(SPEventList*l,SPEventList*n)
{
l->prev->next=n;
n->prev=l->prev;
l->prev=n;
n->next=l;
}
SPEVENT_INLINEvoidSPEVENT_LIST_REMOVE(SPEventList*n)
{
n->next->prev=n->prev;
n->prev->next=n->next;
n->next=n->prev=n;
}
SPEVENT_INLINEintSPEVENT_LIST_LEN(constSPEventList*l)
{
intlen=0;
constSPEventList*p=l;
while(p->next!=l){
p=p->next;
len++;
}
returnlen;
}
#defineSPEVENT_CONTAINER_OF(ptr,type,member)
((type*)((char*)(ptr)-(unsignedlong)(&((type*)0)->member)))
#defineSPEVENT_LIST_OBJ_INIT(obj){&(obj),&(obj)}
#defineSPEVENT_LIST_ENTRY(node,type,member)
SPEVENT_CONTAINER_OF(node,type,member)
#defineSPEVENT_LIST_FOR_EACH(pos,head)
for(pos=(head)->next;pos!=(head);pos=pos->next)
#defineSPEVENT_LIST_FOR_EACH_SAFE(pos,n,head)
for(pos=(head)->next,n=pos->next;pos!=(head);
pos=n,n=pos->next)
#defineSPEVENT_LIST_FOR_EACH_ENTRY(pos,head,member)
for(pos=SPEVENT_LIST_ENTRY((head)->next,typeof(*pos),member);
&pos->member!=(head);
pos=SPEVENT_LIST_ENTRY(pos->member.next,typeof(*pos),member))
#defineSPEVENT_LIST_FOR_EACH_ENTRY_SAFE(pos,n,head,member)
for(pos=SPEVENT_LIST_ENTRY((head)->next,typeof(*pos),member),
n=SPEVENT_LIST_ENTRY(pos->member.next,typeof(*pos),member);
&pos->member!=(head);
pos=n,n=SPEVENT_LIST_ENTRY(n->member.next,typeof(*n),member))
#defineSPEVENT_LIST_FIRST_ENTRY(ptr,type,member)
SPEVENT_LIST_ENTRY((ptr)->next,type,member)
#endif
- spevent.c文件说明:SPEvent的接口实现;整个逻辑通过链表的嵌套,实现了事件的管理,事件的订阅,事件的发布。
#include"spevent.h"
staticSPEventListg_hublist={0};
staticSPEventHubNode*SPEventFindHubNode(SPEventList*list,constchar*event)
{
SPEventHubNode*hubNode=NULL;
SPEventList*node=NULL;
SPEVENT_LIST_FOR_EACH(node,list){
hubNode=SPEVENT_LIST_ENTRY(node,SPEventHubNode,hubList);
if(hubNode!=NULL&&strcmp(hubNode->event,event)==0){
returnhubNode;
}
}
returnNULL;
}
staticSPEventEventNode*SPEventFindEventNode(SPEventList*eventList,SPEventHandlehandle)
{
SPEventEventNode*eventNode=NULL;
SPEventList*node=NULL;
SPEVENT_LIST_FOR_EACH(node,eventList){
eventNode=SPEVENT_LIST_ENTRY(node,SPEventEventNode,list);
if(eventNode!=NULL&&handle==eventNode->handle){
returneventNode;
}
}
returnNULL;
}
SPEventEventNode*SPEventSubscribe(constchar*event,SPEventHandlehandle)
{
SPEventHubNode*hubNode=NULL;
SPEventEventNode*eventNode=NULL;
hubNode=SPEventFindHubNode(&g_hublist,event);
if(hubNode==NULL){
hubNode=(SPEventHubNode*)SPEVENT_MALLOC(sizeof(SPEventHubNode));
if(hubNode==NULL){
SPEVENT_PRINT("SPEVENThub(%s)mallocfailedrn",event);
returnNULL;
}
memset(hubNode->event,0,SPEVENT_EVENT_NAME_LEN);
memcpy(hubNode->event,event,strlen(event));
SPEVENT_LIST_INSERT_AFTER(&g_hublist,&(hubNode->hubList));
SPEVENT_LIST_INIT(&(hubNode->eventList));
}
eventNode=(SPEventEventNode*)SPEVENT_MALLOC(sizeof(SPEventEventNode));
if(eventNode==NULL){
SPEVENT_PRINT("SPEVENTevent(%s)mallocfailedrn",event);
returnNULL;
}
eventNode->handle=handle;
SPEVENT_LIST_INSERT_AFTER(&hubNode->eventList,&(eventNode->list));
SPEVENT_PRINT("SPEVENTevent(%s)Subscribesuccessrn",event);
returneventNode;
}
boolSPEventUnsubscribe(constchar*event,SPEventEventNode*node)
{
SPEventHubNode*hubNode=NULL;
SPEventEventNode*eventNode=NULL;
hubNode=SPEventFindHubNode(&g_hublist,event);
if(hubNode==NULL){
SPEVENT_PRINT("SPEVENThub(%s)findfailedrn",event);
returnfalse;
}
eventNode=SPEventFindEventNode(&(hubNode->eventList),node->handle);
if(eventNode==NULL){
SPEVENT_PRINT("SPEVENTevent(%s)findfailedrn",event);
returnfalse;
}
SPEVENT_LIST_REMOVE(&(eventNode->list));
SPEVENT_FREE(eventNode);
eventNode=NULL;
SPEVENT_PRINT("SPEVENTevent(%s)Unsubscribesuccessrn",event);
returntrue;
}
boolSPEventPublish(constchar*event,void*payload)
{
SPEventHubNode*hubNode=NULL;
SPEventEventNode*eventNode=NULL;
SPEventList*node=NULL;
hubNode=SPEventFindHubNode(&g_hublist,event);
if(hubNode==NULL){
SPEVENT_PRINT("SPEVENThub(%s)findfailedrn");
returnfalse;
}
SPEVENT_LIST_FOR_EACH(node,&(hubNode->eventList)){
eventNode=SPEVENT_LIST_ENTRY(node,SPEventEventNode,list);
if(eventNode->handle){
eventNode->handle(event,payload);
}
}
SPEVENT_PRINT("SPEVENTevent(%s)Publishsuccessrn",event);
returntrue;
}
voidSPEventClear(void)
{
SPEventHubNode*hubNode=NULL;
SPEventEventNode*eventNode=NULL;
SPEventList*hubList=NULL;
SPEventList*eventList=NULL;
SPEVENT_LIST_FOR_EACH(hubList,&g_hublist){
hubNode=SPEVENT_LIST_ENTRY(hubList,SPEventHubNode,hubList);
if(hubNode==NULL){
continue;
}
SPEVENT_LIST_FOR_EACH(eventList,&(hubNode->eventList)){
eventNode=SPEVENT_LIST_ENTRY(eventList,SPEventEventNode,list);
if(eventNode==NULL){
continue;
}
SPEVENT_LIST_REMOVE(&(eventNode->list));
SPEVENT_FREE(eventNode);
eventNode=NULL;
}
SPEVENT_LIST_REMOVE(&(hubNode->hubList));
SPEVENT_FREE(hubNode);
hubNode=NULL;
}
}
voidRecvtInfoDump(void)
{
SPEventHubNode*hubNode=NULL;
SPEventEventNode*eventNode=NULL;
SPEventList*hubList=NULL;
SPEventList*eventList=NULL;
inteventNodeCount=0;
SPEVENT_PRINT("SPEVENTlist:rn");
SPEVENT_LIST_FOR_EACH(hubList,&g_hublist){
hubNode=SPEVENT_LIST_ENTRY(hubList,SPEventHubNode,hubList);
if(hubNode==NULL){
continue;
}
SPEVENT_PRINT("SPEVENTevent(%s):",hubNode->event);
eventNodeCount=0;
SPEVENT_LIST_FOR_EACH(eventList,&(hubNode->eventList)){
eventNode=SPEVENT_LIST_ENTRY(eventList,SPEventEventNode,list);
if(eventNode==NULL){
continue;
}
eventNodeCount++;
}
SPEVENT_PRINT("%drn",eventNodeCount);
}
}
voidSPEventInit(void)
{
SPEVENT_LIST_INIT(&g_hublist);
}
voidSPEventDeinit(void)
{
SPEventClear();
}
- spevent.h文件说明:SPEvent的接口申明及SPEvent相关接口体的定义。
#ifndef__SPEVENT_H__
#define__SPEVENT_H__
#include"spevent_def.h"
typedefvoid(*SPEventHandle)(constchar*event,void*payload);
typedefstruct
{
SPEventHandlehandle;
SPEventListlist;
}SPEventEventNode;
typedefstruct
{
charevent[SPEVENT_EVENT_NAME_LEN];
SPEventListeventList;
SPEventListhubList;
}SPEventHubNode;
voidSPEventInit(void);
voidSPEventDeinit(void);
SPEventEventNode*SPEventSubscribe(constchar*event,SPEventHandlehandle);
boolSPEventUnsubscribe(constchar*event,SPEventEventNode*node);
boolSPEventPublish(constchar*event,void*payload);
voidSPEventClear(void);
voidRecvtInfoDump(void);
#endif
超精简的SPEvent组件,实例:
#include
voidSPEventHandle1(constchar*event,void*payload)
{
SPEVENT_PRINT("Event1:%s,payload:%s",event,payload);
}
voidSPEventHandle2(constchar*event,void*payload)
{
SPEVENT_PRINT(ent2:%s,payload:%s",event,payload);
}
intmain()
{
SPEventInit();
SPEventEventNode*eventNode1=SPEventSubscribe("Rice",SPEventHandle1);
if(eventNode1!=NULL)
{
SPEventPublish("Rice","hello");
}else{
rlog_e("SPEVENTsubscribeeventfailed");
}
SPEventEventNode*eventNode2=SPEventSubscribe("Rice",SPEventHandle2);
if(eventNode2!=NULL)
{
SPEventPublish("Rice","world");
}else{
rlog_e("SPEVENTsubscribeeventfailed");
}
RecvtInfoDump();
SPEventUnsubscribe("Rice",eventNode1);
SPEventPublish("Rice","Hello world");
RecvtInfoDump();
return0;
}
- 结果:
SPEVENTevent(Rice)Subscribesuccess
Event1:Rice,payload:hello
SPEVENTevent(Rice)Publishsuccess
SPEVENTevent(Rice)Subscribesuccess
Event2:Rice,payload:world
Event1:Rice,payload:world
SPEVENTevent(Rice)Publishsuccess
SPEVENTlist:
SPEVENTevent(Rice):2
SPEVENTevent(Rice)Unsubscribesuccess
Event2:Rice,payload:Hello world
SPEVENTevent(Rice)Publishsuccess
SPEVENTlist:
SPEVENTevent(Rice):1
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。
举报投诉
-
组件
+关注
关注
1文章
520浏览量
17964 -
MQTT
+关注
关注
5文章
655浏览量
22751
发布评论请先 登录
相关推荐
RabbitMQ中的发布订阅模型
多个消费者同时接受到,消费者接收到的信息一致。 发布订阅模型适合于做模块之间的异步通信。 img 适用场景 发送并记录日志信息 springcloud的config组件里面通知配置自动更新 缓存同步
![RabbitMQ中的<b class='flag-5'>发布</b><b class='flag-5'>订阅</b>模型](https://file1.elecfans.com/web2/M00/A6/05/wKgaomURKNqAKmJEAAAvsLJOHyk055.jpg)
基于ArkTS语言的OpenHarmony APP应用开发:公共事件的订阅和发布
应用程序提供订阅、发布、退订公共事件的能力。
公共事件从系统角度可分为:系统公共事件和自定义公共事件。
系统公共事件:CES内部定义的公共事件,只有系统应用和系统服务才能发布,例如HAP安装,更新,卸载
发表于 09-18 13:16
MQTT协议介绍之一:发布/订阅
,Pub / Sub将正在接收消息(称为订户)的另一客户端(或更多客户端)发送特定消息(称为发布者)的客户端去耦,这意味着发布者和订阅者不了解彼此的存在,有一个第三个组件,称为代理,由
发表于 08-25 19:58
STM32F107是怎样通过LWIP实现MQTT发布和订阅框架的呢
怎样通过STM32CubeMX配置STM32F107VCTx的demo呢?STM32F107是怎样通过LWIP实现MQTT发布和订阅框架的呢?
发表于 10-27 06:06
NodeMCU实现订阅和发布主题
NodeMCU实现订阅和发布主题。1、要点扫盲1.1 MQTT《MQTT协议--MQTT协议简介及原理》《MQTT协议--MQTT协议解析》1.2 OneNET《NodeMCU学习(十)--发送数据
发表于 11-01 08:37
超精简的按键组件MultiButton概括
Growing up’s a funny thing. Sneaks up on you.长大是件很有趣的事,不经意间就发生了。一、概括项目的仓库大佬的超精简的软件定时器multi_timer已经让人眼前一亮,如今这个按键组件M
发表于 02-28 11:19
YoC组件发布开源操作指南须知
过程中提交代码到组件开发仓库,直到组件功能完成。2.1.5 贡献发布组件开发者将组件贡献合入YoC,需要按照以下章节3进行操作。2.2 yo
发表于 03-09 07:37
请问esp32c3,ble mesh怎么向订阅的分组发布消息?
发布消息,为什么vnd_models模型不可以.有没有更加简单的api,直接传订阅分组地址就可以发布消息的?
发表于 02-13 06:47
请问esp32c3 ble mesh怎么向订阅的分组发布消息?
发布消息,为什么vnd_models模型不可以.有没有更加简单的api,直接传订阅分组地址就可以发布消息的?
发表于 03-06 08:36
基于SOA的发布/订阅系统设计
企业电子商务的迅猛发展已经改变了分布式系统的规模,传统的基于请求/应答的点对
点、同步通信已不能满足大规模动态分布式应用环境。基于SOA 的发布/订阅系统模型
发表于 07-08 08:42
•21次下载
发布/订阅消息传递协议有哪些?为什么这类协议在物联网应用广泛
发布/订阅消息传递协议是一种消息传递模式,其中消息的发布者和订阅者是解耦的,消息的发布者和订阅者
给我两分钟,搞懂发布-订阅模式很轻松!
什么是发布/订阅模式?举一个生活中常见的例子说明:小李到某房产中介提出租房需求,根据需求,房产中介将之前房东发布的出租信息提供给小李选择,小李确定租房后,中介会将信息同步给房东知晓。这是一个典型
![给我两分钟,搞懂<b class='flag-5'>发布</b>-<b class='flag-5'>订阅</b>模式很轻松!](https://file.elecfans.com/web2/M00/3E/6A/pYYBAGJhBGGAGyDYAACBPQuBZQI711.png)
评论