Libev库学习

Posted lsgxeva

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 的selectpollepoll;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 使用三种层级的错误:

  1. 操作系统错误:调用ev_set_syserr_cb所设置的回调。默认行为是调用abort()
  2. 参数错误:调用assert
  3. 内部错误(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_MAJOREV_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_prepareev_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_XXXev_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 中指定的 signal
  • EV_CHILD:ev_child 中指定的 pid 获得了一个状态变化
  • EV_STAT:ev_stat 中指定的 path 的属性修改了
  • EV_IDLE:ev_idle watcher 发现无事可做
  • EV_PREPAREEV_CHECK:所有 ev_prepare watchers 在 loop 开始收集事件前调用;所有ev_check watchers 则在以后调用。回调可在这两个 watchers 中开始/停止相应的 watchers。
  • EV_EMBED:ev_embed watcher
  • EV_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_eventev_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(如kqueueepoll),否则当一个 fd 消失、而新的 fd 进入,占用同一个 fd 号时,libev不知道这是一个新的fd。
  libev 一侧解决的办法是每次调用ev_io_set时,都假定这是一个新的 fd。

使用dup操作 fd 的特殊问题

一些后端(backend)不能注册普通的 fd 事件,只能注册underlying file descriptions,这意味着使用dup()或其他奇怪操作的fd,只能由其中一个被接收到。
  这没有有效的解决办法,除非将后端设置为BACKEND_SELECTEVBACKEND_POLL

关于文件的特殊问题

ev_io对于文件泪说没有什么用,只要文件存在,就立即会有时间。对于stdinstdout,请谨慎使用,确保这两者没有被重定向至文件。

关于 fork 的特殊问题

记得使用ev_loop_fork,并且使用EVFLAG_FORKCHECK。不过对于epollkqueue之外的无需担心。

关于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_WRITEEV_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_suspendev_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)

以下是几种不同应用场景的设置方法:

  1. 绝对计时器:offset 等于绝对时间,interval 为0,reschedule_cb 为 NULL。在这种设置下,时钟只执行一次,不重复
  2. 重复内部时钟:offset 小于等于 interval 值,interval 大于0,reschedule_cb 为 NULL。这种设置下,watcher 永远在每一个(offset + N * interval)超时。
  3. 手动排程模式: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_statdatastruct 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

基本流程

  1. 创建 socket,绑定 socket 地址
  2. Listen socket
  3. 创建一个 watcher,用来承载accept事件
  4. 写一个 callback 用来做实际的accept调用
  5. 创建并初始化一个 watcher 用来从 client 中读取请求
  6. 写一个 callback 用来read
  7. 启动 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 (loop0); // 这里可以直接使用 default loop

技术图片

 

============== End

 

以上是关于Libev库学习的主要内容,如果未能解决你的问题,请参考以下文章

libev个人问题解惑

libev笔记

libev事件库使用笔记

先从一个 libev 的 demo 入手

libev学习

libev