摘要:示例从中进行相对超时机制提供了一个相对超时机制的定时器。所谓的相对,就是说这个定时器的参数是指定以当前时间为基准,延迟多久出发事件。这个非常便于用来提供诸如下一个正午点之类的定时器。
请注意这是 libev 而不是 libevent 的文章!
这篇文章是第三篇,主要讲 libev 里基本集中的 watcher。
本文地址:https://segmentfault.com/a/1190000006679929
这个 watcher 负责检测文件描述符(以下简称fd)是否可写入数据或者是读出数据。最好是将fd设置为非阻塞的。
注意有时候在调用read时是没有数据的(返回0),此时一个一个非阻塞的read会得到EAGAIN错误。
(以下两个特殊问题,是 libev 文档中特别提到的,但是我看不太懂……)
部分系统需要显式地调用close(如kqueue、epoll),否则当一个 fd 消失、而新的 fd 进入,占用同一个 fd 号时,libev不知道这是一个新的fd。
libev 一侧解决的办法是每次调用ev_io_set时,都假定这是一个新的 fd。
一些后端(backend)不能注册普通的 fd 事件,只能注册underlying file descriptions,这意味着使用dup()或其他奇怪操作的fd,只能由其中一个被接收到。
这没有有效的解决办法,除非将后端设置为BACKEND_SELECT或EVBACKEND_POLL
ev_io对于文件泪说没有什么用,只要文件存在,就立即会有时间。对于stdin和stdout,请谨慎使用,确保这两者没有被重定向至文件。
记得使用ev_loop_fork,并且使用EVFLAG_FORKCHECK。不过对于epoll和kqueue之外的无需担心。
只是提醒一下:记得处理SIGPIPE事件。
大多数 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的不同策略
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,并且重启它。第一个参数是首次超时,第二个参数是第二次开始的固定超时时间。
但是这样的方法虽然比较简易,但是时间不稳定,而且开销较大
使用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
这个方式的基本思路是因为许多 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)
使用场景:有成千上万个请求,并且都需要 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 中监听。
注意点在子进程调用 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。
系列篇Libev 官方文档学习笔记(1)——概述和 ev_loop
Libev 官方文档学习笔记(2)——watcher 基础
Libev 官方文档学习笔记(3)——常用 watcher 接口(本文)
使用 libev 构建 TCP 响应服务器的简单流程
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/9424.html
摘要:回调可在这两个中开始停止相应的。即将被销毁已经被异步通知不是发送的信号。此外还需要调用相应的函数。停止,并清空状态。当且仅当时返回。系列篇官方文档学习笔记概述和官方文档学习笔记基础本文官方文档学习笔记常用接口使用构建响应服务器的简单流程 请注意这是 libev 而不是 libevent 的文章! 这篇文章是第二篇,主要讲 libev 里的 watcher 的一些基础操作。 本文地址:h...
摘要:注意这个函数并不是线程安全的。等于新事件的数量。暂停当前的,使其刮起当前的所有工作。两个值均默认为,表示尽量以最小的延迟调用。系列篇官方文档学习笔记概述和本文官方文档学习笔记基础官方文档学习笔记常用接口使用构建响应服务器的简单流程 请注意这是 libev 而不是 libevent 的文章! 自从接触到 libev 之后,就深深赞同作者精简的设计理念,于是就爱上了 libev 这样简单的...
摘要:事件同理准备用来的读取的方法就视乎程序员的实现啦把数据回去原文例子使用的就是,实际上我个人偏爱的是启动这里可以直接使用系列篇官方文档学习笔记概述和官方文档学习笔记基础官方文档学习笔记常用接口使用构建响应服务器的简单流程本文 请注意这是 libev 而不是 libevent 的文章!这篇文章主要是使用具体的例子,说明如何使用 libev。网上不少文章都是照抄示例,一点用都没有……本文将示...
摘要:实线表示该状态的流转需要经过异步等待调用后才能获取相应的状态码或返回值进行检查后才可以进行的状态流转,虚线表示在该状态下即已有足够的变量可进行状态流转。而函数的返回值,则换成一个类型的变量,用于适配异步。 在之前我翻译的官方文档中提到了 MariaDB 提供了对异步 I/O 的支持。那篇文章是一个比较简要的介绍。不过实际适配中,官方也提供了一个完整适配 libevent 的示例代码。本...
摘要:一旦有事件产生可能是一次出现好多个事件,就会按照优先级依次调用每个事件的回调函数。注意,是有超时的,所以一些无法以文件描述符的形式存在的事件也可以有机会被触发。 这一篇主要想跟大家分享一下 Gevent 实现的基础逻辑,也是有同学对这个很感兴趣,所以贴出来跟大家一起分享一下。 Greenlet 我们知道 Gevent 是基于 Greenlet 实现的,greenlet 有的时候也被...
阅读 2524·2021-11-15 11:39
阅读 1600·2021-09-24 09:48
阅读 852·2021-09-22 15:36
阅读 3407·2021-09-10 11:22
阅读 2824·2021-09-07 09:59
阅读 827·2021-09-03 10:28
阅读 517·2021-09-02 15:15
阅读 2613·2021-08-27 16:24