请注意这是 libev 而不是 libevent 的文章!
自从接触到 libev 之后,就深深赞同作者精简的设计理念,于是就爱上了 libev 这样简单的I/O库。此外,libev 的大小也比 libevent 小得多并且自由得多。虽然我在公司的项目用的异步 I/O 库还是以 libevent 和 libubox 为主,但是个人业余的工程中,往往用的是 libev 而不是 libevent。
可惜的是,貌似是因为 libev 是单人维护,而且不支持 Windows 等原因,并不如 libevent 甚至是 libuv 等受欢迎,国内的研究资料也并不多。
但是呢,老子是 Linux / BSD 开发者,我就喜欢!
阅读本文最好有 libevent 基础,因为基本概念和 libevent 是类似的
库 事件循环 具体事件
----------------------------------
libevent event_loop event
libev ev_loop watcher
其他的在这里我就不重复了。
关于 libevent 请参见我的文章:Libevent官方文档学习笔记
本文地址:https://segmentfault.com/a/1190000006173864
Reference
概述
Features
-
ev_io
:支持 Linux 的select
、poll
、epoll
;BSD 的kqueue
;Solaris 的event port mechanisms
-
ev_signal
:支持各种信号处理、同步信号处理 -
ev_timer
:相对事件处理 -
ev_periodic
:排程时间表 -
ev_child
:进程状态变化事件 -
ev_start
:监视文件状态 -
ev_fork
:有限的fork事件支持
时间显示
Libev 使用一个ev_tstamp
数据类型来表示1970年以来的秒数,实际类型是 C 里面的double
类型。
错误事件
Libev 使用三种层级的错误:
- 操作系统错误:调用
ev_set_syserr_cb
所设置的回调。默认行为是调用abort()
- 参数错误:调用
assert
- 内部错误(bug):内部调用
assert
全局(配置)函数
以下函数可以在任意时间调用,用于配置 libev 库:
ev_tstamp ev_time ();
返回当前的时间。
void ev_sleep (ev_tstamp interval);
休眠一段指定的时间。如果interval
小于等于0,则立刻返回。最大支持一天,也就是86400秒
int ev_version_major ();
int ev_version_minor ();
可以调用这两个函数,并且与系统与定义的EV_VERSION_MAJOR
和EV_VERSION_MINOR
作对比,判断是否应该支持该库
unsigned int ev_supported_backends ();
unsigned int ev_recommand_backends ();
unsigned int ev_embeddable_backends ();
返回该 libev 库支持的和建议的后端列表
void ev_set_allocator ( void *(*cb)(void *ptr, long size)throw() );
重新设置realloc
函数。对于一些系统(至少包括 BSD 和 Darwin)的 realloc 函数可能不正确,libev 已经给了替代方案。
void ev_set_syserr_cb ( void (*cb)(const char *msg)throw() );
设置系统错误的 callback。默认调用perror()
并abort()
void ev_feed_signal (int signum)
模拟一个signal
事件出来
控制 event loops 的函数
Event loop 用一个结构体struct ev_loop *
描述。Libev 支持两类 loop,一是 default loop,支持 child process event;动态创建的 event loops 就不支持这个功能
struct ev_loop *ev_default_loop (unsigned int flags);
初始化 default loops。如果已经初始化了,那么直接返回并且忽略 flags。注意这个函数并不是线程安全的。只有这个 loop 可以处理ev_child
事件。
struct ev_loop *ev_loop_new (unsigned int flags);
这个函数是线程安全的。一般而言,每个 thread 使用一个 loop。以下说明 flag 项的各个值:
-
EVFLAG_AUTO
:默认值,常用 -
EVFLAG_NOENV
:指定 libev 不使用LIBEV_FLAGS
环境变量。常用于调试和测试 -
EVFLAG_FORKCHECK
:与ev_loop_fork()
相关,本文暂略 -
EVFLAG_NOINOTIFY
:在ev_stat
监听中不使用inotify
API -
EVFLAG_SIGNALFD
:在ev_signal
监听中使用signalfd
API -
EVFLAG_NOSIGMASK
:使 libev 避免修改 signal mask。这样的话,你要使 signal 是非阻塞的。在未来的 libev 中,这个 mask 将会是默认值。 -
EVBACKEND_SELECT
:通用后端 -
EVBACKEND_POLL
:除了 Windows 之外的所有后端都可以用 -
EVBACKEND_EPOLL
:Linux 后端 -
EVBACKEND_KQUEUE
:大多数 BSD 的后端 -
EVBACKEND_DEVPOLL
:Solaris 8 后端 -
EVBACKEND_PORT
:Solaris 10 后端
void ev_loop_destroy (struct ev_loop *loop);
销毁ev_loop
。注意这里要将所有的 IO 清除光之后再调用,因为这个函数并不中止所有活跃(active)的 IO。部分 IO 不会被清除,比如 signal。这些需要手动清除。这个函数一般和ev_loop_new
一起出现在同一个线程中。
void ev_loop_fork (struct ev_loop *loop);
这个函数导致ev_run
的子过程重设已有的 backend 的 kernel state。重用父进程创建的 loop。可以和pthread_atfork()
配合使用。
需要在每一个需要在 fork 之后重用的 loop 中调用这个函数。必须在恢复之前或者调用ev_run()
之前调用。如果是在fork
之后创建的 loop,不需要调用。
使用 pthread 的代码例如下:
static void post_fork_chuild (void)
{
ev_loop_fork (EV_DEFAULT);
}
...
pthread_atfork (NULL, NULL, post_fork_child);
int ev_is_default_loop (struct ev_loop *loop);
判断当前 loop 是不是 default loop。
unsigned int ev_iteration (struct ev_loop *loop);
返回当前的 loop 的迭代数。等于 libev pool 新事件的数量(?)。这个值对应ev_prepare
和ev_check
调用,并在 prepare 和 check 之间增一。
unsigned int ev_depth (struct ev_loop *loop);
返回ev_run()
进入减去退出次数的差值。
注意,导致ev_run
异常退出的调用(setjmp / longjmp, pthread_cancel, 抛出异常等)均不会导致该值减一。
unsigned int ev_backend (struct ev_loop *loop);
返回EVBACKEND_*
值
ev_tstamp ev_now (loop)
得到当前的“event loop time”。在 callback 调用期间,这个值是不变的。
void ev_new_update (loop)
更新从ev_now()
中返回的时间。不必要的话,不要使用,因为这个函数的开销相对是比较大的。
void ev_suspend (struct ev_loop *loop);
void ev_resume (struct ev_loop *loop);
暂停当前的 loop,使其刮起当前的所有工作。同时其 timeout 也会暂停。如果恢复后,timer 会从上一次暂停状态继续及时——这一点对于实现一些要连同时间也一起冻结的功能时,非常有用。
注意已经 resume 的loop不能再 resume,反之已经 suspend 的 loop 不能再 suspend。
bool ev_run (struct ev_loop *loop, int flags);
初始化 loop 结束后,调用这个函数开始 loop。如果 flags == 0,直至 loop 没有活跃的时间或者是调用了 ev_bread 之后停止。
Loop 可以是异常使能的,你可以在 callback 中调用longjmp
来终端回调并且跳出 ev_run,或者通过抛出 C++ 异常。这些不会导致 ev_depth 值减少。
EVRUN_NOWAIT
会检查并且执行所有未解决的 events,但如果没有就绪的时间,ev_run 会立刻返回。EVRUN_ONCE
会检查所有的 events,在至少每一个 event 都执行了一次事件迭代之后才返回。但有时候,使用ev_prepare
/ev_check
更好。
以下是ev_run
的大致工作流程:
- loop depth ++
- 重设
ev_break
状态 - 在首次迭代之前,调用所有 pending watchers
LOOP:
- 如果置了
EVFLAG_FORKCHECK
,则检查 fork,如果检测到 fork,则排队并调用所有的 fork watchers - 排队并且调用所有 ready 的watchers
- 如果
ev_break
被调用了,则直接跳转至 FINISH - 如果检测到了 fork,则分离并且重建 kernel state
- 使用所有未解决的变化更新 kernel state
- 更新
ev_now
的值 - 计算要 sleep 或 block 多久
- 如果指定了的话,sleep
- loop iteration ++
- 阻塞以等待事件
- 排队所有未处理的I/O事件
- 更新
ev_now
的值,执行 time jump 调整 - 排队所有超时事件
- 排队所有定期事件
- 排队所有优先级高于 pending 事件的 idle watchers
- 排队所有 check watchers
- 按照上述顺序的逆序,调用 watchers (check watchers -> idle watchers -> 定期事件 -> 计时器超时事件 -> fd事件)。信号和 child watchers 视为 fd watchers。
- 如果
ev_break
被调用了,或者使用了EVRUN_ONCE
或者EVRUN_NOWAIT
,则如果没有活跃的 watchers,则 FINISH,否则 continue
FINISH:
- 如果是
EVBREAK_ONE
,则重设 ev_break 状态 - loop depth --
- return
void ev_break (struct ev_loop *loop, how);
中断 loop。参数可以是 EVBREAK_ONE
(执行完一个内部调用后返回)或EVBREAK_ALL
(执行完所有)。
下一次调用 ev_run 的时候,相应的标志会清除
void ev_ref (struct ev_loop *loop);
void ev_unref (struct ev_loop *loop);
类似于 Objective-C 中的引用计数,只要 reference count 不为0,ev_run 函数就不会返回。
在做 start 之后要 unref;stop 之前要 ref。
void ev_set_io_collect_interval (struct ev_loop *loop, ev_tstamp interval);
void ev_set_timeout_collect_interval (struct ev_loop *loop, ev_tstamp interval);
两个值均默认为0,表示尽量以最小的延迟调用 callback。但这是理想的情况,实际上,比如 select 这样低效的系统调用,由于可以一次性读取很多,所以可以适当地进行延时。通过使用比较高的延迟,但是增加每次处理的数据量,以提高 CPU 效率。
void ev_invoke_pending (struct ev_loop *loop);
调用所有的 pending 的 watchers。这个除了可以在 callback 中调用(少见)之外,更多的是在重载的函数中使用。参见下一个函数
void ev_set_invoke_pending_cb (struct ev_loop *loop, void (*invoke_pending_cb(EV_P)));
重载 ev_loop 调用 watchers 的函数。新的回调应调用 ev_invoke_pending
。如果要恢复默认值,则置喙 ev_invoke_pending 即可。
int ev_pending_count (struct ev_loop *loop);
返回当前有多少个 pending 的 watchers。
void ev_set_loop_release_cb (struct ev_loop *loop,
void (*release)(EV_P)throw(),
void (*acquire)(EV_P)throw());
这是一个 lock 操作,你可以自定义 lock。其中 release 是 unlock,acquire 是 lock。release 是在 loop 挂起以等待events 之前调用,并且在开始回调之前调用 acquire。
void ev_set_userdata (struct ev_loop *loop, void *data);
void *ev_userdata (struct ev_loop *loop);
设置 / 读取 loop 中的用户 data。这一点和 libevent 很不同,libevent 的参数 / 用户数据是以 event 为单位的,而 libev 的原生用户数据是以 loop 为单位的。
void ev_verify (struct ev_loop *loop);
验证当前 loop 的设置。如果发现问题,则打印 error msg 并 abort()
。
系列篇
Libev 官方文档学习笔记(1)——概述和 ev_loop(本文)
Libev 官方文档学习笔记(2)——watcher 基础
Libev 官方文档学习笔记(3)——常用 watcher 接口
使用 libev 构建 TCP 响应服务器的简单流程