[英]Event driven design in c
這是一個小理論問題。 想象一個充滿傳感器的設備。 現在,如果傳感器 x 檢測到某些東西,就會發生某些事情。 同時,如果檢測到其他東西,比如兩個傳感器檢測到兩個不同的東西,那么這個設備的行為就必須不同。
從網頁設計(所以 javascript)我了解了事件,例如(使用 jquery) $(".x").on("click", function(){})
或從 angularjs $scope.watch("name_of_var", function())
。
在不使用復雜庫的情況下,是否有可能在 C 中復制這種行為?
謝謝。
我能想到的一個系統是訂閱者通知模型。 你可能有一些東西來處理你的傳感器(例如,一個線程輪詢它以查看是否發生了某些事情)。 當它檢測到一些東西時,任務應該提出一種機制,以便讓外部世界意識到:這是通知過程。
另一方面,只有對你的傳感器感興趣的人才會被通知,因此,訂閱方法應該在這里處理。
現在,困難的部分來了。當傳感器處理程序通知世界時,它不能花太多時間這樣做,否則它可能會錯過其他事件。 因此,必須有一個專用於通知進程的任務(或線程)。 另一方面,訂閱者希望在接收到這樣的通知事件時更新他們的一些數據。 這顯然是一個異步過程,因此訂閱者必須向通知線程提供回調。
最后,你應該用時間戳標記你的事件,這樣接收者就會知道他們得到的事件是否已經過時,是否應該丟棄它。
最后的事情可能看起來像下面的一段代碼:
/*
* Some data structures to begin with
*/
struct event;
struct notifier;
struct subscription;
struct notify_sched;
typedef int (*notify_cbck)(struct event *evt, void *private);
/*
*@type : a value to show the type of event
*@t : the timestamp of the event
*@value : a pointer towards the event data
*/
struct event {
int type;
struct timeval t; // the timestamp
void *value;
};
/*
* @type : the type in which the subscriber is interested
* @cb : the callback that should be run when an event occur
* @cb_data : the data to provide to the callback
* @next,prev : doubly-linked list
*/
struct subscription {
int type;
notify_cbck cb;
void *cb_data;
struct subscription *next, *prev;
};
/*
* This structure gathers the subscriptions of a given type.
* @type : the event type
* @subs : the subscription list
* @mutex : a mutex to protect the list while inserting/removing subscriptions
* @next,prev : link to other typed subscriptions
*/
struct typed_subscription {
int type;
struct subscription *subs;
mutex_t mutex;
struct typed_subscription *next, *prev;
};
/*
* @magic : the ID of the event producer
* @t_subs : the typed_subscription list
* @mutex : a mutex to protect data when (un)registering new types to the producer
* @next, prev : doubly-linked list ...
*/
struct notifier {
int magic;
struct typed_subscription *t_subs;
mutex_t mutex;
struct notifier *next, *prev;
};
/*
* @ntf : the notifiers list
* @mutex : a mutex to protect the ntf list
* @th : something to identify the task that hosts the scheduler
*/
struct notify_sched {
struct notifier *ntf;
mutex_t mutex;
pthread_t th; // I assume it's a classic pthread in this example.
};
我現在沒有時間完成我的答案,稍后我會編輯它以提供完整的示例。 但是從數據結構開始,你應該得到一些想法。 無論如何,希望這有點幫助。
我假設您擁有一個嵌入式系統,可以在單獨的線程中訪問中斷或主要事件循環,否則這是不可能的。
事件處理的基本模型如下:
#define NOEVENT 0
typedef void *(*EventHandler)(void *);
void *doNothing(void *p){/*do nothing absolutely*/ return NULL; }
typedef struct _event{
EventHandler handler;
}Event, *PEvent;
Event AllEvents[1000];
unsigned short counter = 0;
void InitEvents()
{
LOCK(AllEvents);
for(int i = 0; i < 1000; i++){
AllEvents[i].handler = doNothing;
}
UNLOCK(AllEvents);
}
void AddEvent(int EventType, EventHandler ev_handler)
{
LOCK(AllEvents);
AllEvents[EventType].handler = ev_handler;
UNLOCK(AllEvents);
}
void RemoveEvent(int EventType, EventHandler ev_handler)
{
LOCK(AllEvents);
AllEvents[EventType] = doNothing;
UNLOCK(AllEvents); /*to safeguard the event loop*/
}
/*to be run in separate thread*/
void EventLoop()
{
int event = NOEVENT;
EventHandler handler;
while(1){
while(event == NOEVENT)event=GetEvents();
handler = AllEvents[event].handler;
handler();/*perform on an event*/
}
}
對不起,如果這有點天真..但這是我目前能想到的最好的。
因為我發現了這條消息並且我沒有完全找到我的解決方案,所以我根據https://prdeving.wordpress.com/2017/04/03/event-driven-programming-with-c-89/在這里發布了我的代碼為他人。
事件.h
typedef struct s_Arguments {
char *name;
int value;
} Arguments; // Treat this as you please
typedef struct s_Event {
char *name;
Arguments args;
} Event;
typedef struct {
char *name;
void (*handler)(void*);
} Handler;
struct s_EventContainer {
int *exitFlag;
Event *poll;
Handler *listeners;
void (*registerEvent)(char *name, void*);
void (*emit)(char *name, Arguments args);
int listenersc;
int pollc;
};
struct s_EventContainer eventContainer;
void _registerEvent(char *name, void*);
void _emitEvent(char *name, Arguments args);
void popPoll();
void find_and_exec_handler_in_listeners(char *name, Arguments *args);
void * fn_eventsDigest(void * p_Data);
void *testFired(Arguments *args);
事件.c
#include "event.h"
int BLOCK_POP, BLOCK_EMIT;
void _registerEvent(char *name, void *cb) {
LOG("_registerEvent '%s'", name);
if (!eventContainer.listeners) eventContainer.listeners = malloc(sizeof(Handler));
Handler listener = {name, cb};
eventContainer.listeners[eventContainer.listenersc] = listener;
eventContainer.listenersc++;
eventContainer.listeners = realloc(eventContainer.listeners, sizeof(Handler) * (eventContainer.listenersc+1));
}
void _emitEvent(char *name, Arguments args) {
LOG("Emit event '%s' with args value %d", name, args.value);
while(BLOCK_EMIT) asm("nop");
BLOCK_POP = 1;
if (!eventContainer.poll) eventContainer.poll = malloc(sizeof(Event));
Event poll = {name, args};
eventContainer.poll[eventContainer.pollc] = poll;
eventContainer.pollc++;
eventContainer.poll = realloc(eventContainer.poll, sizeof(Event) * (eventContainer.pollc+1));
BLOCK_POP = 0;
}
void popPoll(){
int* temp = malloc((eventContainer.pollc - 1) * sizeof(Event));
memcpy(temp, eventContainer.poll+1, (eventContainer.pollc - 1) * sizeof(Event));
eventContainer.pollc -= 1;
eventContainer.poll = realloc(eventContainer.poll, eventContainer.pollc * sizeof(Event) + sizeof(Event));
memcpy(eventContainer.poll, temp, eventContainer.pollc * sizeof(Event));
temp = NULL;
}
void find_and_exec_handler_in_listeners(char *name, Arguments *args){
int i;
for (i=0; i < eventContainer.listenersc; i++) {
if (strcmp(eventContainer.listeners[i].name, name ) == 0 ) {
(*eventContainer.listeners[i].handler)(args);
}
}
}
void * fn_eventsDigest(void * p_Data) {
struct s_EventContainer *eventContainer = (struct s_EventContainer *)p_Data;
BLOCK_POP = 0;
BLOCK_EMIT = 0;
while(*(eventContainer->exitFlag) == 0) {
if ( eventContainer->pollc > 0 && BLOCK_POP == 0) {
BLOCK_EMIT = 1;
Event ev = eventContainer->poll[0];
find_and_exec_handler_in_listeners(ev.name, &ev.args);
popPoll();
BLOCK_EMIT = 0;
}
usleep(1*1000);
}
printf("Event Loop::exiting...\r\n"); fflush(stdout);
}
void *testFired(Arguments *args) {
LOG("test event fired with value %d \r\n", args->value);
}
主文件
#include "event.h"
#include <pthread.h>
struct s_EventContainer eventContainer = {
&Data.exitFlag, //exit loop
NULL, //poll
NULL, //listeners
_registerEvent,
_emitEvent,
0,
0
};
pthread_t events_thread;
int main(int argc, char** argv) {
eventContainer.registerEvent("test", &testFired);
int ret = pthread_create (&events_thread, NULL, fn_eventsDigest, &eventContainer);
if (ret) {
fprintf (stderr, "%s", strerror (ret));
}
pthread_setname_np(events_thread,"events_thread");
//....
sleep(2);
Arguments args;
args.name = "test";
args.value = 10;
eventContainer.emit("test", args);
pthread_join (events_thread, NULL);
return (EXIT_SUCCESS);
}
我願意接受建議。
對於事件,您需要事件循環,它檢測實際事件(例如,來自網絡的數據)發生,然后生成軟件事件結構並調用適當的事件處理程序,或者在更復雜的系統中,事件處理程序鏈,直到處理程序標記事件被接受。 如果沒有處理程序,或者沒有注冊的處理程序接受該事件,則事件將被忽略。
事件循環通常在一個庫中,除了庫本身可能產生的任何事件之外,該庫還具有用於應用程序的 API 以具有事件處理程序,並發送應用程序特定事件。 基於事件的應用程序的一個問題是,使用兩個都希望擁有自己的事件循環的庫通常很復雜,除非庫開發人員特別注意允許使用庫自身以外的其他事件循環。
除非它是一個非常低級的實時系統,否則事件循環不進行忙等待是至關重要的。 在 Linux/Unix/Posix 代碼中,事件循環通常圍繞 select() 或 poll() 系統函數工作。 當沒有事件時,事件循環調用這個函數,超時匹配下一個定時器事件的時間(如果有定時器事件)。 除了超時之外,如果任何指定的文件句柄(通常是網絡或 IPC 套接字)准備好讀取/寫入/錯誤,以及是否存在未以其他方式處理的中斷,select()/poll() 也將返回. 然后事件循環代碼檢查函數返回的原因,生成並分發必要的事件,當所有事情都完成后,再次調用 select()/poll() 函數。
在基於事件的系統中同樣重要的是,事件處理程序不能阻塞,因為它是由事件循環調用的函數,所以事件循環不會在后台某處繼續,處理程序函數調用是事件循環的一部分。 所以處理函數必須只處理可用數據,最好是快速處理,然后存儲必要的狀態以供稍后繼續,並返回等待下一個事件。 對於必須阻塞的操作,必須啟動另一個線程。 同樣對於長時間的計算,計算必須被切成小塊以允許事件循環也運行,或者計算必須在另一個線程中進行。 GUI 應用程序標題欄中煩人的“無響應”通常意味着:應用程序程序員懶惰/無能並且阻塞了事件循環,因此它無法對操作系統事件做出反應。
所以,是的,使用 C 建立基於事件的系統是很容易的。只要有一個帶有 select()/poll() 的循環,定義事件類型,為事件創建數據結構,並有一個函數指針列表對於每種事件類型,使用新的事件結構作為參數調用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.