Libev 官方文档学习笔记 - 03:常用 watcher 接口
2017-06-22 14:33
387 查看
请注意这是 libev 而不是 libevent 的文章!
这篇文章是第三篇,主要讲 libev 里基本集中的 watcher。
本文地址:https://segmentfault.com/a/1190000006679929
这个 watcher 负责检测文件描述符(以下简称fd)是否可写入数据或者是读出数据。最好是将fd设置为非阻塞的。
注意有时候在调用
(以下两个特殊问题,是 libev 文档中特别提到的,但是我看不太懂……)
部分系统需要显式地调用
fd 消失、而新的 fd 进入,占用同一个 fd 号时,
libev 一侧解决的办法是每次调用
一些后端(backend)不能注册普通的 fd 事件,只能注册
这没有有效的解决办法,除非将后端设置为
记得使用
只是提醒一下:记得处理
大多数 POSIX accpet 实现中在删除因为错误而导致的连接时(如 fd 到达上限)都回产生一个错误的操作,比如使 accept 失败但不拒绝连接,只产生
libev 还是将其标记为 ready 状态。
推荐方法是列出所有的错误并记录下来,或者是暂时关闭 watchers。
其中 events 可以是
Libev 提供了一个相对超时机制的定时器。所谓的“相对”,就是说这个定时器的参数是:指定以当前时间为基准,延迟多久出发事件。这个定时器与基于万年历的日期/时间是无关的,只基于系统单调时间。
下面列出一个以60秒为单位的循环定时器作为例子,来说明使用ev_timer的不同策略
标准设置。或——
这样的设置,当每次有活跃时间时,停止timer,并且重启它。第一个参数是首次超时,第二个参数是第二次开始的固定超时时间。 但是这样的方法虽然比较简易,但是时间不稳定,而且开销较大
使用
上面的初始化完成后,在 callback 里调用:
可以改变 timeout 值,不管 timer 是否 active
这个方式的基本思路是因为许多 timeout 时间都比 interval 大很多,此时要记住上一次活跃的时间,然后再 callback 中检查真正的 timeout
启用这种模式,记得初始化时将
使用场景:有成千上万个请求,并且都需要 timeouts
当 timeout 开始前,计算 timeout 的值,并且将 timeout 放在链表末尾。然后当链表前面的项需要 fire 时。使用
fire 掉。
当有 activity 时,将 timer 从 list 中一处,重算 timeout,并且再附到 list 末尾,确保如果
list 的第一项取出时,更新它
假设在500.9秒的时候请求延时1秒,那么当501秒到来时,可能导致 timeout,这就是“太早”问题。Libev的策略是对于这种情况,在502秒时才执行 timeout。但是这又有“太晚”的问题,请程序员注意.
Suspenged animation,也称为休眠,指的是将机子置于休眠状态。注意不同的机子不同的系统这个行为可能不一样。
其中一种休眠是使得所有程序感觉只是经过了很小的一段时间一般(时间跳跃)
推荐在
Libev使用的时一个内部的单调时钟而不是系统时钟,而
如果repeat为正,这个timer会重复触发,否则只触发一次。
以下是几种不同应用场景的设置方法:
绝对计时器:offset 等于绝对时间,interval 为0,reschedule_cb 为 NULL。在这种设置下,时钟只执行一次,不重复
重复内部时钟:offset 小于等于 interval 值,interval 大于0,reschedule_cb 为 NULL。这种设置下,watcher 永远在每一个(offset + N * interval)超时。
手动排程模式:offset 忽略,reschedule_cb 设置。使用 callback 来返回下次的 trigger 时间。callback 原型为:
例程是:
类似于 Linux 内核的
这个timer非常便于用来提供诸如“下一个正午12点”之类的定时器。
关闭并重启 watcher,参见前文。
返回下一次触发的绝对时间。
在哦你跟一个 loop 可以多次观测同一个 signal,但是无法在多个 loop 中观测同一个 signal。此外,
default loop 中监听。
在子进程调用
POSIX 的不少功能(如sigwait)只有在进程中的所有线程屏蔽了 signal 时才真正生效
为了解决这个问题,如果真的要使用这些功能的话,建议在创建线程之前屏蔽所有的 signal,并且在创建 loops 的时候指定
thread 用来接收 signals。
当接收到
未开始,你甚至可以在 shild 被 fork 之后才加入 child watcher。
Ev_child 的优先级固定是
Pid 如果指定0的话,表示任意子进程。可以在 ev_child 中观察
表示监控中的 pid,只读。
可读写,表示检测到状态变化的 pid
可读写,表示由 rpid 导致的进程的 exit/trace 状态值。
使用 ev_stat 时,监控目标位置上无需存在文件,因为文件从“不存在”变为存在也是一种状态变化。
文件路径必须是绝对路径,不能存在“
Ev_stat 的实现其实只是定期调用
正因为这是轮询操作,所以这个功能不适合做大数据量或者是大并发检测;同时,ev_stat 是异步的。
默认关闭大文件支持(使用32位的
有些系统的文件时间仅精确到秒,这就意味着 ev_stat 无法区分秒以下的变动。
第三个函数使用新的文件 stat 值去更新 stat buffer,使用此函数来使得你做的一些配置更改不会被触发。
只读,代表文件最近一次的状态。
文件上一次的状态
都是只读,字面意义上的意思。
这个功能没有研究过,暂记着把。
从指定的f fd 中指定一个超时事件,这个函数的方便之处在于无需做
Fd 可以小于0,这样就没有 I/O 监控,并且“events”会被忽略。
向一个 fd 发送事件。需要注意的是,这个功能貌似是只能在 loop 内调用才有效,异步地在 loop 的另一个线程直接调用是无效的。
向一个 loop 模拟 signal。参见
Libev 官方文档学习笔记(1)——概述和 ev_loop
Libev 官方文档学习笔记(2)——watcher 基础
Libev 官方文档学习笔记(3)——常用 watcher 接口(本文)
使用 libev 构建 TCP 响应服务器的简单流程
这篇文章是第三篇,主要讲 libev 里基本集中的 watcher。
本文地址:https://segmentfault.com/a/1190000006679929
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 中检查真正的 timeoutev_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。
系列篇
Libev 官方文档学习笔记(1)——概述和 ev_loopLibev 官方文档学习笔记(2)——watcher 基础
Libev 官方文档学习笔记(3)——常用 watcher 接口(本文)
使用 libev 构建 TCP 响应服务器的简单流程
相关文章推荐
- Libev 官方文档学习笔记 - 02:watcher 基础
- iOS学习笔记-143.网络03——自己搭建的后台登陆接口文档
- Log4j2官方文档翻译、学习笔记之三——Layouts的分类及常用类型示例
- Log4j2官方文档翻译、学习笔记之二——Appender的分类及常用类型示例
- Libev 官方文档学习笔记 - 01:概述和 ev_loop
- 学习笔记 --android将数据存放及xml文档常用的 方法
- ProGaurd官方文档学习笔记(一)
- DB2学习笔记 --- IBM官方 DB2数据库操作文档指南
- ES权威指南[官方文档学习笔记]-8
- ES权威指南[官方文档学习笔记]-42 Distributed document store
- ES权威指南[官方文档学习笔记]-34 Creating a new document
- ES权威指南[官方文档学习笔记]-26 Coping with failure
- ES权威指南[官方文档学习笔记]-18 Distributed nature
- ES权威指南[官方文档学习笔记]-53 Search Lite
- 【官方文档】Nginx模块Nginx-Rtmp-Module学习笔记(一) RTMP 命令详解
- ES权威指南[官方文档学习笔记]-14 phrase search
- Libevent 官方文档学习笔记(3. evbuffer部分)
- UINavigationController官方文档学习笔记
- 学习Solidity官方文档的笔记一
- Android TableLayout官方文档 例子学习笔记