Libev库学习
Libev库学习
https://www.cnblogs.com/wunaozai/p/3950249.html Libev库学习(1)
https://www.cnblogs.com/wunaozai/p/3954131.html Libev库学习(2)
https://www.cnblogs.com/wunaozai/p/3955156.html Libev库学习(3)
https://www.cnblogs.com/wunaozai/p/3960494.html Zlib库的安装与使用
https://segmentfault.com/a/1190000006173864 01:概述和 ev_loop
https://segmentfault.com/a/1190000006200077 02:watcher 基础
https://segmentfault.com/a/1190000006679929 03:常用 watcher 接口
https://www.cnblogs.com/gqtcgq/p/7247102.html Libev中的相对时间定时器 ev_timer
https://www.cnblogs.com/gqtcgq/p/7247100.html Libev中的绝对时间定时器 ev_periodic
https://www.cnblogs.com/gqtcgq/p/7247095.html Libev源码分析08:Libev中的信号监视器
http://blog.chinaunix.net/uid-25203957-id-1753940.html Libev 初步
http://blog.chinaunix.net/uid-25203957-id-1760908.html 互斥锁和条件变量
概述
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
APIEVFLAG_SIGNALFD
:在ev_signal
监听中使用signalfd
APIEVFLAG_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()
。
Watcher 解析
以下是一段示意性的代码,使用的是ev_io
:
static void my_cb (struct ev_loop *loop, ev_io *w, int revents)
{
ev_io_stop (w);
ev_break (loop, EVBREAK_ALL);
}
some_main()
{
...
struct ev_loop *loop = ev_default_loop (0);
ev_io stdin_watcher;
ev_init (&stdin_watcher, my_cb);
ev_io_set (&stdin_watcher, STDIN_FILENO, EV_READ);
ev_io_start (loop, &stdin_watcher);
ev_run (loop, 0);
...
}
每一个 watcher 类型有一个附属的 watcher 结构体。(一般是struct ev_XXX
或ev_XXX
)
每一个 watcher 结构都需要用ev_init
初始化,每一个 watcher 都有对应的ev_XXX_set
函数、ev_XXX_start
函数、ev_XXX_stop
函数。在 ev_run 之前进行各个 watcher 的 ev_start。
只要 watcher 是 active,就不能再调用 init。
每个 callback 都有三个参数:loop, watcher, 事件的掩码值。可能的掩码值有:
EV_READ
EV_WRITE
EV_TIMER
:ev_timer 超时EV_PERIODIC
:ev_periodic 超时EV_SIGNAL
:某线程接收了 ev_signal 中指定的 signalEV_CHILD
:ev_child 中指定的 pid 获得了一个状态变化EV_STAT
:ev_stat 中指定的 path 的属性修改了EV_IDLE
:ev_idle watcher 发现无事可做EV_PREPARE
,EV_CHECK
:所有 ev_prepare watchers 在 loop 开始收集事件前调用;所有ev_check watchers 则在以后调用。回调可在这两个 watchers 中开始/停止相应的 watchers。EV_EMBED
:ev_embed watcherEV_CLEANUP
:event loop 即将被销毁EV_ASYNC
:asuny watcher 已经被异步通知EV_CUSTOM
:不是 libev 发送的信号。参见ev_feed_event
EV_ERROR
:在 libev 内存不够用时可能产生;fd 被外部关闭时也可能产生
通用 watcher 函数
void ev_init (ev_TYPE *watcher, callback)
使用这个宏初始化 watcher。此外还需要调用相应的 ev_XXX_set
函数。参见下文:
void ev_TYPE_set (ev_TYPE *watcher, [args])
设置指定类型的 wetaher。init 函数必须在此之前被调用一次,此后可以设置任意次的 set 函数。
不能对一个 active 的 watcher 调用此函数,但 pending 可以。比如:ev_io w;
ev_init (&w, my_cb);
ev_io_set (&w, STDIN_FILENO, EV_READ);
void ev_TYPE_set (ev_TYPE *watcher, callback, [args])
这个宏将 init 和 set 糅合在一起使用
void ev_TYPE_start (loop, ev_TYPE *watcher)
开始(激活)指定的 watcher。如果 watcher 已经是 active,则调用无效。
void ev_TYPE_stop (loop, ev_TYPE *watcher)
停止 watcher,并清空 pending 状态。如果要释放一个 Watcher,最好都显式地调用 stop。
bool ev_is_active (ev_TYPE *watcher)
如果 watcher 被执行了一次 start,并且未被 stop,则返回 true。
bool ev_is_pending (ev_TYPE *watcher)
当且仅当 watcher pending 时返回 true。(如:有未决的事件,但是 callback 未被调用)
callback ev_cb (ev_TYPE *watcher)
void ev_set_cb (ev_TYPE *watcher, callback)
读 / 写 callback
void ev_set_priority (ev_TYPE *watcher, int priority)
int ev_priority (ev_TYPE *watcher)
Priority 是一个介于EV_MAXPRI
(默认2)和EV_MIN_PRI
(默认-2)之间的值。数值越高越优先被调用。但除了 ev_idle,每一个 watcher 都会被调用。
当 watcher 是 active 或 pending 时并不能修改。
实际上 priority 大于-2到2的范围也是没问题的。
void ev_invoke (loop, ev_TYPE *watcher, int revents);
使用指定的参数调用 callback
int ev_clear_pending (loop, ev_TYPE *watcher);
清除指定 watcher 的 pending 状态,并且返回 revents 位。如果 watcher 不是 pending 则返回0
void ev_feed_event (loop, ev_TYPE *watcher, int revents)
模拟一个事件。参见ev_feed_fd_event
和ev_feed_signal_event
Watcher 状态
除了前文提及的 active 和 pending 状态之外,本小节描述了更加详细的 watcher 状态。
initialized
:通过调用ev_TYPE_init
对 watcher 进行初始化,这是注册到 loop 之前的必要步骤。可以再次调用 ev_TYPE_init 进行操作。
started
/running
/active
:调用ev_TYPE_start
之后的状态,并且开始等待事件。在这个状态下,除了特别提及的少数情况之外,它不能存取、移动、释放,只能维持着对它的指针。
pending
:当 watcher 是 active 并且一个让 watcher 感兴趣的事件到来,那么 watcher 进入 pending。这个状态的 watcher 可以 access,但不能存取、移动、释放。
stopped
:调用ev_TYPE_stop
,此时状态与 initialized 相同。
ev_io:直接操作fd
这个 watcher 负责检测文件描述符(以下简称fd)是否可写入数据或者是读出数据。最好是将fd设置为非阻塞的。
注意有时候在调用read
时是没有数据的(返回0),此时一个一个非阻塞的read
会得到EAGAIN
错误。
(以下两个特殊问题,是 libev 文档中特别提到的,但是我看不太懂……)
失踪的 fd 的特殊问题
部分系统需要显式地调用close
(如kqueue
、epoll
),否则当一个 fd 消失、而新的 fd 进入,占用同一个 fd 号时,libev
不知道这是一个新的fd。
libev 一侧解决的办法是每次调用ev_io_set
时,都假定这是一个新的 fd。
使用dup
操作 fd 的特殊问题
一些后端(backend)不能注册普通的 fd 事件,只能注册underlying file descriptions
,这意味着使用dup()
或其他奇怪操作的fd,只能由其中一个被接收到。
这没有有效的解决办法,除非将后端设置为BACKEND_SELECT
或EVBACKEND_POLL
关于文件的特殊问题
ev_io
对于文件泪说没有什么用,只要文件存在,就立即会有时间。对于stdin
和stdout
,请谨慎使用,确保这两者没有被重定向至文件。
关于 fork 的特殊问题
记得使用ev_loop_fork
,并且使用EVFLAG_FORKCHECK
。不过对于epoll
和kqueue
之外的无需担心。
关于SIGPIPE的问题
只是提醒一下:记得处理SIGPIPE
事件。
关于accept
一个无法接受的连接
大多数 POSIX accpet 实现中在删除因为错误而导致的连接时(如 fd 到达上限)都回产生一个错误的操作,比如使 accept 失败但不拒绝连接,只产生ENFILE
错误。但这个会导致 libev 还是将其标记为 ready 状态。
推荐方法是列出所有的错误并记录下来,或者是暂时关闭 watchers。
相关函数
void ev_io_init (ev_io *, callback, int fd, int events)
void ev_io_set (ev)io *, int fd, int events)
其中 events 可以是EV_WRITE
和EV_READ
的组合。
示例
static void stdin_readable_db (struct ev_loop *loop,
ev_io *w,
int revents)
{
ev_io_stop (loop, w)
...... // 从 w->fd 中进行read
}
......
some_init_func ()
{
......
struct ev_loop *loop = ev_default_init (0);
ev_io stdin_readable;
ev_io_init (&stdin_readable, stdin_readable_db , STDIN_FILENO, EV_READ);
ev_io_start (loop, &stdin_readable);
ev_run (loop, 0);
...
}
ev_timer:相对超时机制
Libev 提供了一个相对超时机制的定时器。所谓的“相对”,就是说这个定时器的参数是:指定以当前时间为基准,延迟多久出发事件。这个定时器与基于万年历的日期/时间是无关的,只基于系统单调时间。
循环定时器设计
下面列出一个以60秒为单位的循环定时器作为例子,来说明使用ev_timer的不同策略
1. 使用标准的初始化和停止 API 来重设
ev_timer_init (timer, callback, 60.0, 6.0);
ev_timer_start (loop, timer)
标准设置。或——
ev_timer_stop (loop, timer);
ev_timer_set (timer, 60.0, 0.0);
ev_timer_start (loop, timer)
这样的设置,当每次有活跃时间时,停止timer,并且重启它。第一个参数是首次超时,第二个参数是第二次开始的固定超时时间。
但是这样的方法虽然比较简易,但是时间不稳定,而且开销较大
2. 使用ev_timer_again
重设
使用ev_timer_again
,可以忽略ev_timer_start
ev_init (timer, callback);
timer->repeat = 60.0;
ev_timer_again (loop, start);
上面的初始化完成后,在 callback 里调用:
timer->repeat = 60.0;
ev_timer_again (loop, timer);
可以改变 timeout 值,不管 timer 是否 active
3. 让 timer 超时,但视情况重新配置
这个方式的基本思路是因为许多 timeout 时间都比 interval 大很多,此时要记住上一次活跃的时间,然后再 callback 中检查真正的 timeout
ev_tstamp g_timeout = 60.0;
ev_tstamp g_last_activity;
ev_timer g_timer;
static void callback (EV_P_ev_timer *w, int revents)
{
ev_tstamp after = g_last_activity - ev_now(EV_A) + g_timeout;
// 如果小于零,表示时间已经发生了,已超时
if (after < 0.0) {
...... // 执行 timeout 操作
}
else {
// callback 被调用了,但是却有一些最近的活跃操作,说明未超时
// 此时就按照需要设置的新超时事件来处理
ev_timer_set (w, after, 0.0);
ev_timer_start (loop, g_timer);
}
}
启用这种模式,记得初始化时将g_last_activity
设置为ev_now
,并且调用一次callback (loop, &g_timer, 0)
;当活跃时间到来时,只需修改全局的 timeout 变量即可,然后再调用一次 callback
g_timeout = new_value
ev_timer_stop (loop, &timer)
callback (loop, &g_timer, 0)
4. 为 timer 使用双向链表
使用场景:有成千上万个请求,并且都需要 timeouts
当 timeout 开始前,计算 timeout 的值,并且将 timeout 放在链表末尾。然后当链表前面的项需要 fire 时。使用ev_timer
来将其 fire 掉。
当有 activity 时,将 timer 从 list 中一处,重算 timeout,并且再附到 list 末尾,确保如果ev_timer
已经被 list 的第一项取出时,更新它
“太早”的问题
假设在500.9秒的时候请求延时1秒,那么当501秒到来时,可能导致 timeout,这就是“太早”问题。Libev的策略是对于这种情况,在502秒时才执行 timeout。但是这又有“太晚”的问题,请程序员注意.
“假死”问题
Suspenged animation,也称为休眠,指的是将机子置于休眠状态。注意不同的机子不同的系统这个行为可能不一样。
其中一种休眠是使得所有程序感觉只是经过了很小的一段时间一般(时间跳跃)
推荐在SIGTSTP
处理中调用ev_suspend
和ev_resume
其他注意点
ev_now_update()
的开销很大,请谨慎使用
Libev使用的时一个内部的单调时钟而不是系统时钟,而ev_timer
则是基于系统时钟的,所以在做比较的时候两者不同步。
相关函数
void ev_timer_init (ev_timer *, callback, ev_tstamp after, ev_tstamp repeat);
void ev_timer_set (ev_timer *, ev_tstamp after, ev_tstamp repeat);
如果repeat为正,这个timer会重复触发,否则只触发一次。
void ev_timer_again (loop, ev_timer *)
ev_tstamp ev_timer_remaining (loop, ev_timer *)
ev_periodic:基于日历的定时器
相关函数
void ev_periodic_init (ev_periodic *, callback, ev_tstamp offset,
ev_tstamp interval, reschedule_cb)
void ev_periodic_set (ev_periodic *, ev_tstamp offset,
ev_tstamp interval, reschedule_cb)
以下是几种不同应用场景的设置方法:
- 绝对计时器:offset 等于绝对时间,interval 为0,reschedule_cb 为 NULL。在这种设置下,时钟只执行一次,不重复
- 重复内部时钟:offset 小于等于 interval 值,interval 大于0,reschedule_cb 为 NULL。这种设置下,watcher 永远在每一个(offset + N * interval)超时。
- 手动排程模式:offset 忽略,reschedule_cb 设置。使用 callback 来返回下次的 trigger 时间。callback 原型为:
ev_tstamp (*reschedule_cb)(ev_periodic *w, ev_tstamp now);
例程是:
static ev_tstamp my_scheduler (...)
{
return now + 60.0;
}
类似于 Linux 内核的jiffies
,返回下一个时间点。
这个timer非常便于用来提供诸如“下一个正午12点”之类的定时器。
void ev_periodic_again (loop, ev_periodic *)
关闭并重启 watcher,参见前文。
ev_tstamp ev_periodic_at (ev_periodic *)
返回下一次触发的绝对时间。
ev_signal:捕获 signal 事件
在哦你跟一个 loop 可以多次观测同一个 signal,但是无法在多个 loop 中观测同一个 signal。此外,SIGCHILD
只能在 default loop 中监听。
注意点
关于继承 fork / execve / ptherad_create 的问题
在子进程调用 exec
之前,应当将 signal mask 重设为你所需的默认值。最简单的方法就是子进程做一个pthread_atfork()
来重设。
关于线程信号处理
POSIX 的不少功能(如sigwait)只有在进程中的所有线程屏蔽了 signal 时才真正生效
为了解决这个问题,如果真的要使用这些功能的话,建议在创建线程之前屏蔽所有的 signal,并且在创建 loops 的时候指定EVFLAG_NOSIGMASK
,然后制定一个 thread 用来接收 signals。
相关函数
void _ev_signal_init (ev_signal *, callback, int signum)
void ev_signal_set (ev_signal *, int signum)
ev_child:子进程退出事件
当接收到SIGCHILD
事件时,child watcher 触发。大部分情况下,子进程退出或被杀掉。只要这个 watcher 的 loop 未开始,你甚至可以在 shild 被 fork 之后才加入 child watcher。
Ev_child 的优先级固定是EV_MAXPRI
。
void ev_chile_init (ev_child *, callback, int pid, int trace)
void ev_child_set (ev_child *, int pid, int trace)
Pid 如果指定0的话,表示任意子进程。可以在 ev_child 中观察rstatus
成员来了解子进程状态。
int pid;
表示监控中的 pid,只读。
int rpid;
可读写,表示检测到状态变化的 pid
int tstatus;
可读写,表示由 rpid 导致的进程的 exit/trace 状态值。
ev_stat:监控文件属性变化
使用 ev_stat 时,监控目标位置上无需存在文件,因为文件从“不存在”变为存在也是一种状态变化。
文件路径必须是绝对路径,不能存在“./
”或“../
”。
Ev_stat 的实现其实只是定期调用stat()
来判断文件属性的变化,所以可以指定检查周期。指定0的话会使用默认事件周期。
正因为这是轮询操作,所以这个功能不适合做大数据量或者是大并发检测;同时,ev_stat 是异步的。
大文件支持
默认关闭大文件支持(使用32位的stat
)。如果要使用大文件支持(ABI),libev 的作者在这里吐槽,说你要游说操作系统的发布方去支持……囧rz
关于文件时间
有些系统的文件时间仅精确到秒,这就意味着 ev_stat 无法区分秒以下的变动。
相关函数和数据成员
void ev_stat_init (ev_stat *, callback, const char *path, ev_tstamp interval);
void ev_stat_set (ev_stat *, const char *path, ev_tstamp interval);
void ev_stat_stat (loop, ev_stat *);
第三个函数使用新的文件 stat 值去更新 stat buffer,使用此函数来使得你做的一些配置更改不会被触发。
ev_statdata attr
只读,代表文件最近一次的状态。ev_statdata
和struct stat
基本是相通的。
ev_statdata prev
文件上一次的状态
ev_tstamp interval
const char *path
都是只读,字面意义上的意思。
ev_idle:无事可做时的事件
void ev_idle_init (ev_idle *, callback)
这个功能没有研究过,暂记着把。
其他事件(仅记录)
ev_prepare 和 ev_check
ev_embed
ev_fork
ev_cleanup
ev_asunc
其他函数
void ev_once (loop, int fd, int events, ev_tstamp timeout, callback)
从指定的f fd 中指定一个超时事件,这个函数的方便之处在于无需做 alloc
/conf
/start
/stop
/free
。
Fd 可以小于0,这样就没有 I/O 监控,并且“events”会被忽略。
void ev_feed_event (loop, int fd, int revents);
向一个 fd 发送事件。需要注意的是,这个功能貌似是只能在 loop 内调用才有效,异步地在 loop 的另一个线程直接调用是无效的。
void ev_feed_signal_event (loop, signum)
向一个 loop 模拟 signal。参见 ev_feed_signal
。
Reference
Create tcp echo server using libev
基本流程
- 创建 socket,绑定 socket 地址
Listen
socket- 创建一个 watcher,用来承载
accept
事件 - 写一个 callback 用来做实际的
accept
调用 - 创建并初始化一个 watcher 用来从 client 中读取请求
- 写一个 callback 用来
read
- 启动 event loop
创建 socket 并绑定 address
注意:原文例子中未显示的是,应当将 fd 设置为非阻塞的。带非阻塞设置的代码如下:
some_init_func()
{
...
sd = socket (PF_INET, SOCK_STREAM, 0);
flags = fcntl (sd, F_GETEL, 0);
fcntl (sd, F_SETEL, flags | O_NONBLOCK);
bzero (&addr, sizeof(addr));
... // 设置 Address 和 port
bind (sd, (struct sockaddr *)(&addr), sizeof(addr));
...
}
监听端口
some_init_func()
{
...
listen (sd, 2);
...
}
准备用来accept()
的 watcher
some_init_func()
{
...
ev_io_init (&w_accept, accept_cb, sd, EV_READ);
ev_io_start (loop, &w_accept);
...
}
回调函数如下:
static void accept_cb (struct ev_loop *loop,
struct ev_io *watcher,
int revents)
{
...
client_sd = accept (watcher->fd, // accept() 调用,接受传入连接
(struct sockaddr *)(&client_addr),
&client_len);
...
w_client = (struct ev_io *)malloc(sizeof(struct ev_io)); // 为 read watcher 准备内存
...
ev_io_init (w_client, read_cb, client_sd, EV_READ); // 这里就只示例 read 事件了。write 事件同理
ev_io_start (loop, w_client);
}
准备用来read()
的 callback
static void read_cb (struct ev_loop *loop,
struct ev_io *watcher,
int revents)
{
...
readCount = recv (watcher->fd, buffer, BUFFER_SIZE, 0); // 读取的方法就视乎程序员的实现啦
send (watcher->fd, buffer, readCount, 0); // 把数据 echo 回去
...
}
原文例子使用的就是recv
/send
,实际上我个人偏爱的是read
/write
启动 event loop
ev_loop (loop, 0); // 这里可以直接使用 default loop
============== End