0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

超精簡(jiǎn)的訂閱發(fā)布事件組件--SPEvent

Rice嵌入式開(kāi)發(fā)技術(shù)分享 ? 來(lái)源: Rice 嵌入式開(kāi)發(fā)技術(shù)分 ? 作者: Rice 嵌入式開(kāi)發(fā) ? 2023-01-22 13:45 ? 次閱讀

概述

  • 本文主要描述一個(gè)超精簡(jiǎn)的訂閱發(fā)布事件組件--SPEvent。
  • 在實(shí)際開(kāi)發(fā)過(guò)程中,一個(gè)事件的產(chǎn)生會(huì)產(chǎn)生很多業(yè)務(wù)的執(zhí)行,或者多個(gè)事件都要執(zhí)行同一個(gè)業(yè)務(wù)的執(zhí)行。在這種場(chǎng)景下有兩種做法:
  1. 將同一個(gè)事件的業(yè)務(wù)放在一個(gè)函數(shù)中,然后事件產(chǎn)生的時(shí)候執(zhí)行對(duì)應(yīng)的函數(shù)。
  2. 某個(gè)業(yè)務(wù)需要哪個(gè)事件,它自己監(jiān)聽(tīng)對(duì)應(yīng)事件并執(zhí)行。
  • 顯然,第一種策略會(huì)將業(yè)務(wù)與業(yè)務(wù)之間耦合在一起,對(duì)后期維護(hù)是非常痛苦的;第二種顯然會(huì)更加有優(yōu)勢(shì),不同業(yè)務(wù)完全解耦,獨(dú)立完成事件的業(yè)務(wù)。
  • 第二種策略的方式,實(shí)際在軟件架構(gòu)中經(jīng)??吹?,比如MQTT的通信(通過(guò)訂閱對(duì)應(yīng)的topic去監(jiān)聽(tīng)對(duì)應(yīng)內(nèi)容)。
  • 有了上述的需求,作者做了一個(gè)超精簡(jiǎn)的訂閱發(fā)布事件組件。整個(gè)邏輯很簡(jiǎn)單。

超精簡(jiǎn)的SPEvent組件,實(shí)現(xiàn)方法

  1. 整個(gè)訂閱發(fā)布事件機(jī)制,引入兩個(gè)東西:EventHub和EventNode。

  • EventHub:每一個(gè)事件類(lèi)型都為一個(gè)EventHub,然后掛在HubList中。
  • EventNode:每一個(gè)訂閱事件的業(yè)務(wù)為一個(gè)EventNode,然后掛在對(duì)應(yīng)的EventHub中。
  • 整個(gè)訂閱發(fā)布事件機(jī)制圍繞著EventHub和EventNode,特點(diǎn):

  • 資源占用極小,接口操作簡(jiǎn)單
  • 事件支持動(dòng)態(tài)訂閱,動(dòng)態(tài)注銷(xiāo)。
  • SPEvent采用雙向鏈表進(jìn)行維護(hù)整個(gè)訂閱-發(fā)布邏輯

  • SPEvent一定存在一個(gè)EventHubList鏈表來(lái)維護(hù)事件類(lèi)型,它默認(rèn)是沒(méi)有任何EventHub節(jié)點(diǎn),
  • 訂閱事件流程:當(dāng)訂閱者訂閱事件之后,如果事件不存在,則申請(qǐng)一個(gè)EventHub,并將EventHub掛在到EventHubList鏈表中;然后申請(qǐng)一個(gè)EventNode,及將對(duì)應(yīng)EventNode掛在EventNodeList鏈表。
  • 發(fā)布事件流程:當(dāng)發(fā)布者發(fā)布事件時(shí),會(huì)從EventHubList中查詢(xún)有沒(méi)有對(duì)應(yīng)的EventHub,如果EventHub存在,則將事件消息發(fā)布給對(duì)應(yīng)EventHub下所有EventNode。
  • 注銷(xiāo)事件訂閱流程:當(dāng)訂閱者注銷(xiāo)已經(jīng)訂閱的事件,會(huì)從EventHubList中查詢(xún)有沒(méi)有對(duì)應(yīng)的EventHub,如果EventHub存在,則將對(duì)應(yīng)EventNode從EventHub中刪除。

9e3996c2-9815-11ed-92c9-dac502259ad0.png

超精簡(jiǎn)的SPEvent組件,接口說(shuō)明:

函數(shù) 說(shuō)明
SPEventInit 初始化函數(shù)
SPEventDeinit 去初始化函數(shù)
SPEventSubscribe 訂閱事件函數(shù)
SPEventUnsubscribe 注銷(xiāo)訂閱事件函數(shù)
SPEventPublish 發(fā)布事件消息
SPEventClear 清除事件池
RecvtInfoDump 導(dǎo)出事件池信息

超精簡(jiǎn)的SPEvent組件,代碼實(shí)現(xiàn)

  1. 整個(gè)代碼接口存在3個(gè)文件:spevent.c、spevent.h、spevent_def.h。其中:
  2. spevent_def.h文件說(shuō)明:定義了屏蔽平臺(tái)相關(guān)接口的宏和定義了雙向量表操作的宏定義。雙向量表在SPEvent中式至關(guān)重要數(shù)據(jù)結(jié)構(gòu):
#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
  1. spevent.c文件說(shuō)明:SPEvent的接口實(shí)現(xiàn);整個(gè)邏輯通過(guò)鏈表的嵌套,實(shí)現(xiàn)了事件的管理,事件的訂閱,事件的發(fā)布。
#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();
}
  1. spevent.h文件說(shuō)明:SPEvent的接口申明及SPEvent相關(guān)接口體的定義。
#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

超精簡(jiǎn)的SPEvent組件,實(shí)例:

#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;
}

  • 結(jié)果:
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

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 組件
    +關(guān)注

    關(guān)注

    1

    文章

    499

    瀏覽量

    17771
  • MQTT
    +關(guān)注

    關(guān)注

    5

    文章

    640

    瀏覽量

    22358
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    RabbitMQ中的發(fā)布訂閱模型

    多個(gè)消費(fèi)者同時(shí)接受到,消費(fèi)者接收到的信息一致。 發(fā)布訂閱模型適合于做模塊之間的異步通信。 img 適用場(chǎng)景 發(fā)送并記錄日志信息 springcloud的config組件里面通知配置自動(dòng)更新 緩存同步
    的頭像 發(fā)表于 09-25 14:30 ?478次閱讀
    RabbitMQ中的<b class='flag-5'>發(fā)布</b><b class='flag-5'>訂閱</b>模型

    基于ArkTS語(yǔ)言的OpenHarmony APP應(yīng)用開(kāi)發(fā):公共事件的訂閱發(fā)布

    應(yīng)用程序提供訂閱、發(fā)布、退訂公共事件的能力。 公共事件從系統(tǒng)角度可分為:系統(tǒng)公共事件和自定義公共事件。 系統(tǒng)公共事件:CES內(nèi)部定義的公共事件,只有系統(tǒng)應(yīng)用和系統(tǒng)服務(wù)才能發(fā)布,例如HAP安裝,更新,卸載
    發(fā)表于 09-18 13:16

    MQTT協(xié)議介紹之一:發(fā)布/訂閱

    ,Pub / Sub將正在接收消息(稱(chēng)為訂戶(hù))的另一客戶(hù)端(或更多客戶(hù)端)發(fā)送特定消息(稱(chēng)為發(fā)布者)的客戶(hù)端去耦,這意味著發(fā)布者和訂閱者不了解彼此的存在,有一個(gè)第三個(gè)組件,稱(chēng)為代理,由
    發(fā)表于 08-25 19:58

    Redis的發(fā)布訂閱機(jī)制

    Redis之發(fā)布訂閱機(jī)制
    發(fā)表于 06-11 13:21

    STM32F107是怎樣通過(guò)LWIP實(shí)現(xiàn)MQTT發(fā)布訂閱框架的呢

    怎樣通過(guò)STM32CubeMX配置STM32F107VCTx的demo呢?STM32F107是怎樣通過(guò)LWIP實(shí)現(xiàn)MQTT發(fā)布訂閱框架的呢?
    發(fā)表于 10-27 06:06

    NodeMCU實(shí)現(xiàn)訂閱發(fā)布主題

    NodeMCU實(shí)現(xiàn)訂閱發(fā)布主題。1、要點(diǎn)掃盲1.1 MQTT《MQTT協(xié)議--MQTT協(xié)議簡(jiǎn)介及原理》《MQTT協(xié)議--MQTT協(xié)議解析》1.2 OneNET《NodeMCU學(xué)習(xí)(十)--發(fā)送數(shù)據(jù)
    發(fā)表于 11-01 08:37

    stm32實(shí)現(xiàn)mqtt的發(fā)布訂閱有哪些注意事項(xiàng)

    stm32實(shí)現(xiàn)mqtt的發(fā)布訂閱遇到的問(wèn)題和需要注意的地方本文中使用的是正點(diǎn)原子的stm32f407系列,使用的是KEIL+stm32cubemx的構(gòu)建方式,其中硬件驅(qū)動(dòng)和lwip可以通過(guò)
    發(fā)表于 12-13 07:28

    精簡(jiǎn)的按鍵組件MultiButton概括

    Growing up’s a funny thing. Sneaks up on you.長(zhǎng)大是件很有趣的事,不經(jīng)意間就發(fā)生了。一、概括項(xiàng)目的倉(cāng)庫(kù)大佬的精簡(jiǎn)的軟件定時(shí)器multi_timer已經(jīng)讓人眼前一亮,如今這個(gè)按鍵組件M
    發(fā)表于 02-28 11:19

    YoC組件發(fā)布開(kāi)源操作指南須知

    過(guò)程中提交代碼到組件開(kāi)發(fā)倉(cāng)庫(kù),直到組件功能完成。2.1.5 貢獻(xiàn)發(fā)布組件開(kāi)發(fā)者將組件貢獻(xiàn)合入YoC,需要按照以下章節(jié)3進(jìn)行操作。2.2 yo
    發(fā)表于 03-09 07:37

    請(qǐng)問(wèn)esp32c3,ble mesh怎么向訂閱的分組發(fā)布消息?

    發(fā)布消息,為什么vnd_models模型不可以.有沒(méi)有更加簡(jiǎn)單的api,直接傳訂閱分組地址就可以發(fā)布消息的?
    發(fā)表于 02-13 06:47

    請(qǐng)問(wèn)esp32c3 ble mesh怎么向訂閱的分組發(fā)布消息?

    發(fā)布消息,為什么vnd_models模型不可以.有沒(méi)有更加簡(jiǎn)單的api,直接傳訂閱分組地址就可以發(fā)布消息的?
    發(fā)表于 03-06 08:36

    基于SOA的發(fā)布/訂閱系統(tǒng)設(shè)計(jì)

    企業(yè)電子商務(wù)的迅猛發(fā)展已經(jīng)改變了分布式系統(tǒng)的規(guī)模,傳統(tǒng)的基于請(qǐng)求/應(yīng)答的點(diǎn)對(duì) 點(diǎn)、同步通信已不能滿足大規(guī)模動(dòng)態(tài)分布式應(yīng)用環(huán)境。基于SOA 的發(fā)布/訂閱系統(tǒng)模型
    發(fā)表于 07-08 08:42 ?21次下載

    鏈表的替代品Vector組件介紹

    SPEvent實(shí)際不會(huì)存在刪改的動(dòng)作,顯然鏈表的優(yōu)點(diǎn)在這個(gè)組件中無(wú)法體現(xiàn)優(yōu)勢(shì)。
    的頭像 發(fā)表于 03-07 10:41 ?643次閱讀

    鏈表的替代品--Vector組件

    概述 在之前的一篇文章中,作者寫(xiě)了一個(gè)事件組件-- 精簡(jiǎn)訂閱發(fā)布事件組件--
    的頭像 發(fā)表于 04-06 15:39 ?485次閱讀

    發(fā)布/訂閱消息傳遞協(xié)議有哪些?為什么這類(lèi)協(xié)議在物聯(lián)網(wǎng)應(yīng)用廣泛

    發(fā)布/訂閱消息傳遞協(xié)議是一種消息傳遞模式,其中消息的發(fā)布者和訂閱者是解耦的,消息的發(fā)布者和訂閱
    的頭像 發(fā)表于 04-18 15:33 ?426次閱讀