簡體   English   中英

c語言中的事件驅動設計

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM