1. libev
库概述
libev
是一个高性能的事件循环/事件驱动库,用于 UNIX 系统(包括 Linux 和 macOS),它提供了异步事件处理的能力,如定时器、I/O 事件、信号、子进程等。
2. libev
事件循环与观察者模式
2.1.1 ev_loop
// ev_loop结构体的大致如下: struct ev_loop { // 当前时间戳 ev_tstamp ev_rt_now; // 指向不同监听器集合的指针(也有可能是动态数组或链表) ev_watcher **watchers[EV__COUNT]; // 假设是数组,用于存储不同类型的观察者 // 后端相关的字段 int backend; // 当前使用的后端类型 int backend_fd; // 后端使用的文件描述符 // 事件循环的状态标志 int flags; // 可能包含多种状态,如是否正在运行、是否应该停止等 // 触发队列,用于存储已经准备好但尚未处理的事件 // 注意:实际中这个部分可能会更复杂,因为libev支持多种优先级和事件调度策略 ev_watcher *pending[/* 假设的优先级数量 */]; };
Libev
的事件循环对象由struct ev_loop
定义。
2.1.2 ev_run
开启事件循环。
ev_run
()内部大致执行流程。
- 递增loop深度. - 重置ev_break状态. - 在第1次迭代之前,呼叫所有pending中的观察者。 开始循环(LOOP): - 如果定义了EVFLAG_FORKCHECK宏, 则每次都检查fork. - 如果检测到了fork, 则根据队列调用所有fork watcher. - 根据队列调用所有prepare watcher. - 如果调用了ev_break, 则直接结束事件循环. - 如果fork已经被调用, 分离并且重新创建内核状态避免进程干扰. - 更新未修改的内核状态. - 更新event loop time (ev_now()). - 如果有必要, 计算休眠和阻塞时间(EVRUN_NOWAIT或者没有活跃的观察者则不会导致sleep). - 如果指定了I/O休眠时间, 则这里会执行. - 递增循环迭代计数器. - 阻塞进程等待事件来来临. - 根据队列调用所有活跃I/O事件 - 更新event loop time(ev_now()) 避免时间跳跃. - 根据队列处理超时定时器(ev_timer). - 根据队列处理周期定时器(ev_periodic). - 处理具有高优先级的idle事件. - 根据队列处理所有(ev_check). - signal、async、child 被作为I/O观察者实现并串行化执行. - 如果调用ev_break或flags为EVRUN_ONCE、EVRUN_NOWAIT或没有活跃事件, 则结束事件循环. 否则重复上述步奏. 结束(FINISH): - 根据情况重置EV_BREAK的状态. - 递减loop深度 - ev_run返回.
2.3 使用实例
// 导入libev相关头文件 #include <ev.h> int main (void) { // 使用已定义的宏来获取默认的事件循环对象, 也可以根据自己的需求返回特定的ev_loop对象. struct ev_loop *loop = EV_DEFAULT; // 开始运行事件循环 ev_run (loop, 0); // 如果事件循环退出, 那将会执行到这里. return 0; }
2.2 ev_io
从宏中剥离出来ev_io
结构体定义如下:
typedef struct ev_io { int active; //启动状态 int pending; //挂起状态 int priority; //优先级 void *data; //参数 void (*cb)(struct ev_loop *loop, struct ev_io *w, int revents);//回调函数 struct ev_watcher_list *next;//链表结构 int fd; /* 这里的fd,events就是派生类的私有成员,分别表示监听的文件fd和触发的事件(可读还是可写) */ int events; } ev_io;
ev_io
是这I/O
观察者, 通过注册组合事件来监视状态变更。I/O
观察者会在每次事件循环迭代中检查注册的文件描述符(fd
)是否可读、可写。若I/O
观察者观测到了注册的文件描述符可读时(假设注册文件描述符状态为EV_READ),会进入回调函数做出相应的处理。
2.2.1 使用实例
注册一个来自控制台(Stdin
)的ev_io
输入事件, 并将输其输出返回到控制台。
#include <ev.h> #include <stdio.h> #include <unistd.h> //stdin观察者相对应的回调函数,当观察者检测到文件描述符发生相关事件的变化之后触发。 void stdin_cb(struct ev_loop *loop, ev_io *w, int revents) { char buffer[128]; memset(buffer, 0, 128); read(w->fd, buffer,sizeof(buffer)); puts(buffer); printf("等待输入: \n"); } int main (void) { // 使用已定义的宏来获取默认的事件循环对象, 也可以根据自己的需求返回特定的ev_loop对象. struct ev_loop *loop = EV_DEFAULT; //定义一个stdin观察者,用来监测文件描述符发生的变化。 ev_io stdin_watcher; // 在启动一个I/O观察者之前, 需要先初始化它I/O观察者,并绑定相应回调处理函数。 ev_io_init (&stdin_watcher, stdin_cb, 0, EV_READ); // 启动I/O观察者之后,若观察者检测到在stdin文件描述符变为可读后触发回调函数。 ev_io_start (loop, &stdin_watcher); printf("等待输入: \n"); // 开始运行事件循环 ev_run (loop, 0); // 如果事件循环退出, 那将会执行到这里. return 0; }
2.3 ev_timer
从宏中剥离出来ev_timer
结构体定义如下:
typedef struct ev_watcher_time { int active; //启动状态 int pending; //挂起状态 int priority; //优先级 void *data; //参数 void (*cb)(struct ev_loop *loop, struct ev_watcher_time *w, int revents);//回调函数 ev_tstamp at; /* 这个at就是派生类中新的自有成员 ,表示的是at时间触发 */ } ev_watcher_time;
ev_timer
是相对计时器观察者, 通过设置指定的超时时间与可选的重复触发时间.
2.3.1 使用实例
创建一个3
秒后超时, 之后每隔1
秒超时的示例:
#include <ev.h> #include <stdio.h> #include <unistd.h> // 当超时时间到达, 这个回调将会被触发. static void timeout_cb (struct ev_loop *loop, ev_timer *w, int revents) { puts ("timeout"); } int main (void) { // 可以使用已定义的宏来获取默认的事件循环, 当然你也可以根据自己的需求创建指定的. struct ev_loop *loop = EV_DEFAULT; ev_timer timeout_watcher; ev_timer_init (&timeout_watcher, timeout_cb, 3, 1); ev_timer_start (loop, &timeout_watcher); // 开始运行事件循环 ev_run (loop, 0); // 如果事件循环退出, 那将会执行到这里. return 0; }
2.4 ev_signal
从宏中剥离出来ev_signal
结构体定义如下:
typedef struct ev_signal { int active; //启动状态 int pending; //挂起状态 int priority; //优先级 void *data; //参数 void (*cb)(struct ev_loop *loop, struct ev_signal *w, int revents); //回调函数 struct ev_watcher_list *next; //链表结构 int signum; /** 这个signum就是派生类中新的自有成员 ,表示的是接收到的信号 */ } ev_signal;
ev_signal
是将异步的信号转化为对文件描述符的同步处理。
注意:同一个信号(signal)可以用多个ev_signal
观察者监视,但是这些观察者只能位于同一个事件循环中(ev_loop
)。
2.4.1 使用实例
// 只需导入单个头文件 #include <ev.h> #include <stdio.h> // 当使用了键盘的组合键`CTRL`+`C`之后回调会被调用. static void sig_cb (struct ev_loop *loop, ev_signal *w, int revents) { puts ("结束信号已触发! Bye"); //结束事件循环 ev_break(loop, EVBREAK_ALL); } int main (void) { // 可以使用已定义的宏来获取默认的事件循环, 当然你也可以根据自己的需求创建指定的. struct ev_loop *loop = EV_DEFAULT; ev_signal sigint; ev_signal_init(&sigint, sig_cb, SIGINT); ev_signal_start(loop, &sigint); // 开始运行事件循环 ev_run (loop, 0); // 如果事件循环退出, 那将会执行到这里. return 0; }
2.5 ev_async
ev_async
观察者允许从其他线程或信号处理程序中“唤醒”事件循环,并请求执行一个回调函数。这种机制能够跨线程处理异步事件,不用在多个线程之间共享复杂的状态或数据结构。
2.5.1 使用实例
libev
中使用 ev_async
来处理跨线程的异步事件。
// 定义一个结构体来保存线程和事件循环之间的共享数据 typedef struct { struct ev_loop *loop; ev_async async_watcher; pthread_t thread_id; } AsyncContext; static void async_cb(struct ev_loop *loop, ev_async *w, int revents) { printf("Async callback called\n"); ev_break(loop, EVBREAK_ALL); } static void *async_thread(void *arg) { AsyncContext *context = (AsyncContext *)arg; // 发送异步事件请求 ev_async_send(context->loop, &context->async_watcher); // 异步线程完成任务后退出 return NULL; } int main() { AsyncContext context; context.loop = EV_DEFAULT; // 初始化ev_async ev_async_init(&context.async_watcher, async_cb); ev_async_start(context.loop, &context.async_watcher); // 创建并启动异步处理线程 pthread_create(&context.thread_id, NULL, async_thread, &context); // 运行事件循环,等待异步事件 ev_run(context.loop, 0); return 0; }
3.观察者状态
3.1 Initialised
(已初始化状态)
1.在将观察者注册到时间循环之前,必须对其进行初始化。这种状体通过ev_TYPE_init
(ev_signal_init
,ev_timer_init
,ev_io_init
)来设置观察者状态。
2.在初始化状态下,观察者在事件循环适宜的一块内存,可以随意移动、释放、重用等(前提是内存保持不变)
3.2 Started/Running/Active
(已启动/正在运行/活动)
启用观察者(ev_TYPE_start
),观察者会成为事件循环的属性,积极等待事件。在等待的条件下,观察者不能被访问、移动、释放。
3.3 Pending
(挂起状态)
当观察者处于活动状态的时候(Active),并且检测到事件的变化,则观察者会进入挂起状态。
3.4 Stopped
(停止状态)
在释放观察者之前,要保证观察者不是挂起态或者活动态 ,可以通过显示调用(ev_TYPE_stop
)让观察者处于停止状态(初始化状态)。
4.观察者优先级模型
可以通过ev_set_priority
设置观察者优先级,一次来决定观察者事件回调的调用顺序。
事件循环有两种优先级模型:
1.lock-out model
(锁定模式):高优先级观察者接受到事件,低优先级监视器不会被调用。
2.only-for-ordering model
(排序模式):在单个事件循环迭代中对回调调用进行排序:较高优先级的观察者在较低优先级的观察器之前被调用,但它们都在轮询新事件之前被调用。
Libev
对除空闲观察者(ev_idle
)(使用锁定模型)之外的所有观察者使用第二种(仅用于排序)模型。
4.1使用实例
#include <ev.h> #include <stdio.h> #include <stdlib.h> // 高优先级回调函数 static void high_priority_cb(struct ev_loop *loop, ev_timer *w, int revents) { printf("High Priority Timer: %f seconds\n", w->repeat); // 可以在这里停止事件循环或者进行其他操作 } // 低优先级回调函数 static void low_priority_cb(struct ev_loop *loop, ev_timer *w, int revents) { printf("Low Priority Timer: %f seconds\n", w->repeat); // 可以在这里停止事件循环或者进行其他操作 } int main() { struct ev_loop *loop = EV_DEFAULT; // 使用默认事件循环 // 创建并初始化高优先级定时器 ev_timer high_priority_timer; ev_timer_init(&high_priority_timer, high_priority_cb, 0., 1.0); // 1秒后触发 high_priority_timer.priority = 2; // 设置优先级为最高 ev_timer_start(loop, &high_priority_timer); // 创建并初始化低优先级定时器 ev_timer low_priority_timer; ev_timer_init(&low_priority_timer, low_priority_cb, 0., 2.0); // 2秒后触发 low_priority_timer.priority = -2; // 设置优先级为最低 ev_timer_start(loop, &low_priority_timer); // 启动事件循环 ev_run(loop, 0); return 0; }
5.libev
的线程安全性和可重入性
5.1线程安全性
只要是操作不事件循环(ev_loop
)实例,libev
是线程安全的。但是如果对同一个ev_loop
实例的并发调用,需要通过同步机制(如互斥锁、条件变量等)来确保一次只有一个线程可以调用。
5.2可重入性
可重入性指的是在单个线程中,可以在不同的栈帧安全地嵌套调用libev
的函数。