Event driven design in c

15

想象一个充满传感器的设备。现在,如果传感器 x 检测到某个东西,应该发生某事。同时,如果检测到其他东西,比如两个传感器检测到两个不同的东西,那么这个设备必须表现出不同的行为。

从网页设计(即 JavaScript)中,我了解到了事件,例如(使用 jQuery)$(".x").on("click", function(){})或者从 AngularJS 中的 $scope.watch("name_of_var", function())

有没有可能在 C 语言中复制这种行为,而不使用复杂的库?


管理刚才描述的硬件情况,无论使用库(无论库有多复杂)与否,都将是复杂的。使用C语言更接近底层。 - WhozCraig
2
如果你需要复杂的行为而又不想使用复杂的库,那就得自己编写复杂的代码 :) - Sergey Kalinichenko
5
您想要异步处理。这需要传感器的驱动程序引发中断,您需要在其发生时做出响应。您没有提到操作系统,但如果传感器连接是通过套接字进行的,则可以使用文件(UNIX示例:)aio或select()因为套接字是文件。 - jim mcnamara
2
类似于 libevlibeventlibuv 这样的库可以用作事件循环驱动框架,但你仍然需要在其上做很多工作。 - Joe
我会在man页面中查看ucontext库。特别是swapcontext功能。它可以为您提供创建类似js的异步事件驱动设计的能力。 - Steve Freed
显示剩余3条评论
4个回答

10
我想到的一个系统是订阅-通知模型。你可以有一个处理传感器的东西(例如,一个轮询它以查看是否发生了什么的线程)。当它检测到某些事情时,任务应该引发一种机制,以便让外界知道:这是通知过程。
另一方面,只有对您的传感器感兴趣的人才应该收到通知,因此,必须有一种订阅方法来处理这个问题。 现在,困难的部分出现了。当传感器处理程序通知世界时,它不能花费太多时间,否则可能会错过其他事件。因此,必须有一个专门用于通知过程的任务(或线程)。另一方面,订户希望在接收到此类已通知事件时更新其某些数据。这显然是一个异步进程,因此订户将不得不向通知线程提供一个回调函数。 最后,您应该使用时间戳标记事件,这样接收者就会知道他们获得的事件是否已过时,以及他们是否应该将其丢弃。
最终的东西可能类似于下面的代码片段:

数据结构

/*
 * 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.
};

    

7
我假设您拥有一个嵌入式系统,可以访问中断或在单独的线程中运行主要事件循环,否则这是不可能的。
以下是事件处理的基本模型:
#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*/
  }
}

0
对于事件,您需要事件循环,它会检测实际事件(比如来自网络的数据)的发生,然后生成软件事件结构并调用适当的事件处理程序,或者在更复杂的系统中,一系列事件处理程序,直到处理程序标记事件被接受。如果没有处理程序,或者没有注册的处理程序接受事件,则事件将被忽略。
通常,事件循环在库中,该库具有应用程序API以拥有事件处理程序,并发送应用程序特定事件,除了库本身可能产生的任何事件。基于事件的应用程序的一个问题是,使用两个都想要拥有自己事件循环的库通常很复杂,除非库开发人员特别注意允许使用其他事件循环而不是库自己的事件循环。

除非是非常低级的实时系统,否则事件循环不应该进行繁忙等待。在Linux/Unix/Posix代码中,事件循环通常围绕select()或poll()系统函数工作。当没有事件时,事件循环将使用与下一个计时器事件(如果有计时器事件)匹配的超时调用此函数。除了超时之外,select()/poll()还将返回是否准备好读取/写入/错误的指定文件句柄(通常是网络或IPC套接字),以及是否存在未经处理的中断。然后,事件循环代码检查函数返回的原因,生成和分派必要的事件,并在完成所有操作后再次调用select()/poll()函数。

在基于事件的系统中,重要的一点是,事件处理程序不能阻塞,因为它是由事件循环调用的函数,所以事件循环不会在后台的某个地方继续执行,处理程序函数调用是事件循环的一部分。因此,处理程序函数必须仅处理可用数据,最好快速处理,然后存储必要的状态以便稍后继续,并返回等待下一个事件。对于必须阻塞的操作,必须启动另一个线程。对于长时间的计算,计算必须被分成小块,以允许事件循环运行,或者计算必须在另一个线程中进行。烦人的“未响应”通常意味着应用程序员懒惰/无能,阻塞了事件循环,因此无法响应操作系统事件。
因此,是的,使用C创建基于事件的系统相当容易。只需在其中使用select()/poll()循环,定义事件类型,为事件创建数据结构,并为每个事件类型具有要调用的函数指针列表,该列表以新事件结构作为参数。

0

因为我发现这个信息,而我并没有找到我的解决方案,所以我基于https://prdeving.wordpress.com/2017/04/03/event-driven-programming-with-c-89/将我的代码发布在这里供其他人使用。

event.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);

event.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);
}

main.c

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

我愿意听取建议。


最大的问题是内存泄漏。将temp设置为NULL不会释放该内存。我还会避免通过使队列只能增长来进行如此多的reallocs;除了具有事件计数之外,还具有最大_so_far并在需要时使用它来重新分配内存。此外,在向上移动队列时,您不需要做整个temp的事情,可以直接使用memcpy。或者设置最大队列长度,并以循环方式使用队列,这样可以完全避免reallocs和memcpys。最后,我不会使用字符串标识符,主要是为了避免strcmp。 - MerseyViking

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接