(转)Libevent(2)— event、event_base
转自:http://name5566.com/4198.html
参考文献列表:
http://www.wangafu.net/~nickm/libevent-book/
此文编写的时候,使用到的 Libevent 为 2.0.21。本文略过了关于 event 优先权和超时相关的讨论。
创建和销毁 event_base
event_base 是首先需要被创建出来的对象。event_base 结构持有了一个 event 集合。如果 event_base 被设置了使用锁,那么它在多个线程中可以安全的访问。但是对 event_base 的循环(下面会马上解释什么是“对 event_base 的循环”)只能在某个线程中执行。如果你希望多个线程进行循环,那么应该做的就是为每一个线程创建一个 event_base。
event_base 存在多个后端可以选择(我们也把 event_base 后端叫做 event_base 的方法):
- select
- poll
- epoll
- kqueue
- devpoll
- evport
- win32
在我们创建 event_base 的时候,Libevent 会为我们选择最快的后端。创建 event_base 通过函数 event_base_new() 来完成:
- // 创建成功返回一个拥有默认设置的 event base
- // 创建失败返回 NULL
- struct event_base *event_base_new(void);
- // 查看 event_base 实际上使用到的后端
- const char *event_base_get_method(const struct event_base *base);
对于大多数程序来说,默认设置已经够用了。
event_base 的释放使用函数:
- void event_base_free(struct event_base *base);
事件循环(event loop)
event_base 会持有一组 event(这是我们前面说到的),换而言之就是说,我们可以向 event_base 中注册 event(具体如何注册本文先不谈)。如果我们向 event_base 中注册了一些 event,那么就可以让 Libevent 开始事件循环了:
- // 指定一个 event_base 并开始事件循环
- // 此函数内部被实现为一个不断进行的循环
- // 此函数返回 0 表示成功退出
- // 此函数返回 -1 表示存在未处理的错误
- int event_base_dispatch(struct event_base *base);
默认的事件循环会在以下的情况停止(也就是 event_base_dispatch 会返回):
- 如果 base 中没有 event,那么事件循环将停止
- 调用 event_base_loopbreak(),那么事件循环将停止
- 调用 event_base_loopexit(),那么事件循环将停止
- 如果出现错误,那么事件循环将停止
事件循环会检测是否存在活跃事件(之前已经介绍过活跃事件这一术语:http://name5566.com/4190.html),若存在活跃事件,那么调用事件对应的回调函数。
停止事件循环的可以通过移除 event_base 中的 event 来实现。如果你希望在 event_base 中存在 event 的情况下停止事件循环,可以通过以下函数完成:
- // 这两个函数成功返回 0 失败返回 -1
- // 指定在 tv 时间后停止事件循环
- // 如果 tv == NULL 那么将无延时的停止事件循环
- int event_base_loopexit(struct event_base *base,
- const struct timeval *tv);
- // 立即停止事件循环(而不是无延时的停止)
- int event_base_loopbreak(struct event_base *base);
这里需要区别一下 event_base_loopexit(base, NULL) 和 event_base_loopbreak(base):
- event_base_loopexit(base, NULL) 如果当前正在为多个活跃事件调用回调函数,那么不会立即退出,而是等到所有的活跃事件的回调函数都执行完成后才退出事件循环
- event_base_loopbreak(base) 如果当前正在为多个活跃事件调用回调函数,那么当前正在调用的回调函数会被执行,然后马上退出事件循环,而并不处理其他的活跃事件了
有时候,我们需要在事件的回调函数中获取当前的时间,这时候你不需要调用 gettimeofday() 而是使用 event_base_gettimeofday_cached()(你的系统可能实现 gettimeofday() 为一个系统调用,你可以避免系统调用的开销):
- // 获取到的时间为开始执行此轮事件回调函数的时间
- // 成功返回 0 失败返回负数
- int event_base_gettimeofday_cached(struct event_base *base,
- struct timeval *tv_out);
如果我们需要(为了调试)获取被注册到 event_base 的所有的 event 和它们的状态,调用 event_base_dump_events() 函数:
- // f 表示输出内容的目标文件
- void event_base_dump_events(struct event_base *base, FILE *f);
event
现在开始详细的讨论一下 event。在 Libevent 中 event 表示了一组条件,例如:
- 一个文件描述符可读或者可写
- 超时
- 出现一个信号
- 用户触发了一个事件
event 的相关术语:
- 一个 event 通过 event_new() 创建出来,那么它是已初始化的,另外 event_assign() 也被用来初始化 event(但是有它特定的用法)
- 一个 event 被注册到(通过 add 函数添加到)一个 event_base 中,其为 pending 状态
- 如果 pending event 表示的条件被触发了,那么此 event 会变为活跃的,其为 active 状态,则 event 相关的回调函数被调用
- event 可以是 persistent(持久的)也可以是非 persistent 的。event 相关的回调函数被调用之后,只有 persistent event 会继续转为 pending 状态。对于非 persistent 的 event 你可以在 event 相关的事件回调函数中调用 event_add() 使得此 event 转为 pending 状态
event 常用 API:
- // 定义了各种条件
- // 超时
- #define EV_TIMEOUT 0x01
- // event 相关的文件描述符可以读了
- #define EV_READ 0x02
- // event 相关的文件描述符可以写了
- #define EV_WRITE 0x04
- // 被用于信号检测(详见下文)
- #define EV_SIGNAL 0x08
- // 用于指定 event 为 persistent
- #define EV_PERSIST 0x10
- // 用于指定 event 会被边缘触发(Edge-triggered 可参考 http://name5566.com/3818.html)
- #define EV_ET 0x20
- // event 的回调函数
- // 参数 evutil_socket_t --- event 关联的文件描述符
- // 参数 short --- 当前发生的条件(也就是上面定义的条件)
- // 参数 void* --- 其为 event_new 函数中的 arg 参数
- typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
- // 创建 event
- // base --- 使用此 event 的 event_base
- // what --- 指定 event 关心的各种条件(也就是上面定义的条件)
- // fd --- 文件描述符
- // cb --- event 相关的回调函数
- // arg --- 用户自定义数据
- // 函数执行失败返回 NULL
- struct event *event_new(struct event_base *base, evutil_socket_t fd,
- short what, event_callback_fn cb,
- void *arg);
- // 释放 event(真正释放内存,对应 event_new 使用)
- // 可以用来释放由 event_new 分配的 event
- // 若 event 处于 pending 或者 active 状态释放也不会存在问题
- void event_free(struct event *event);
- // 清理 event(并不是真正释放内存)
- // 可用于已经初始化的、pending、active 的 event
- // 此函数会将 event 转换为非 pending、非 active 状态的
- // 函数返回 0 表示成功 -1 表示失败
- int event_del(struct event *event);
- // 用于向 event_base 中注册 event
- // tv 用于指定超时时间,为 NULL 表示无超时时间
- // 函数返回 0 表示成功 -1 表示失败
- int event_add(struct event *ev, const struct timeval *tv);
一个范例:
- #include <event2/event.h>
- // event 的回调函数
- void cb_func(evutil_socket_t fd, short what, void *arg)
- {
- const char *data = arg;
- printf("Got an event on socket %d:%s%s%s%s [%s]",
- (int) fd,
- (what&EV_TIMEOUT) ? " timeout" : "",
- (what&EV_READ) ? " read" : "",
- (what&EV_WRITE) ? " write" : "",
- (what&EV_SIGNAL) ? " signal" : "",
- data);
- }
- void main_loop(evutil_socket_t fd1, evutil_socket_t fd2)
- {
- struct event *ev1, *ev2;
- struct timeval five_seconds = {5, 0};
- // 创建 event_base
- struct event_base *base = event_base_new();
- // 这里假定 fd1、fd2 已经被设置好了(它们都被设置为非阻塞的)
- // 创建 event
- ev1 = event_new(base, fd1, EV_TIMEOUT | EV_READ | EV_PERSIST, cb_func,
- (char*)"Reading event");
- ev2 = event_new(base, fd2, EV_WRITE | EV_PERSIST, cb_func,
- (char*)"Writing event");
- // 注册 event 到 event_base
- event_add(ev1, &five_seconds);
- event_add(ev2, NULL);
- // 开始事件循环
- event_base_dispatch(base);
- }
Libevent 能够处理信号。信号 event 相关的函数:
- // base --- event_base
- // signum --- 信号,例如 SIGHUP
- // callback --- 信号出现时调用的回调函数
- // arg --- 用户自定义数据
- #define evsignal_new(base, signum, callback, arg) \
- event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
- // 将信号 event 注册到 event_base
- #define evsignal_add(ev, tv) \
- event_add((ev),(tv))
- // 清理信号 event
- #define evsignal_del(ev) \
- event_del(ev)
在通常的 POSIX 信号处理函数中,不少函数是不能被调用的(例如,不可重入的函数),但是在 Libevent 中却没有这些限制,因为信号 event 设定的回调函数运行在事件循环中。另外需要注意的是,在同一个进程中 Libevent 只能允许一个 event_base 监听信号。
性能相关问题
Libevent 提供了我们机制来重用 event 用以避免 event 在堆上的频繁分配和释放。相关的接口:
- // 此函数用于初始化 event(包括可以初始化栈上和静态存储区中的 event)
- // event_assign() 和 event_new() 除了 event 参数之外,使用了一样的参数
- // event 参数用于指定一个未初始化的且需要初始化的 event
- // 函数成功返回 0 失败返回 -1
- int event_assign(struct event *event, struct event_base *base,
- evutil_socket_t fd, short what,
- void (*callback)(evutil_socket_t, short, void *), void *arg);
- // 类似上面的函数,此函数被信号 event 使用
- #define evsignal_assign(event, base, signum, callback, arg) \
- event_assign(event, base, signum, EV_SIGNAL|EV_PERSIST, callback, arg)
已经初始化或者处于 pending 的 event,首先需要调用 event_del() 后再调用 event_assign()。
这里我们进一步认识一下 event_new()、event_assign()、event_free()、event_del()
event_new() 实际上完成了两件事情:
- 通过内存分配函数在堆上分配了 event
- 使用 event_assign() 初始化了此 event
event_free() 实际上完成了两件事情:
- 调用 event_del() 进行 event 的清理工作
- 通过内存分配函数在堆上释放此 event
(转)Libevent(2)— event、event_base的更多相关文章
- libevent核心-event和event_base结构体
参考:http://blog.csdn.net/yusiguyuan/article/category/2171081/2 http://blog.csdn.net/sparkliang/articl ...
- libevent之event
就如libevent官网上所写的“libevent - an event notification library”,libevent就是一个基于事件通知机制的库,可以看出event是整个库的核心.e ...
- libevent book——event | Gaccob的博客
libevent book——event | Gaccob的博客 libevent book——event 发表于 2013 年 2 月 22 日 由 gaccob 原文地址:http://www.w ...
- Libevent源码分析—event, event_base
event和event_base是libevent的两个核心结构体,分别是反应堆模式中的Event和Reactor.源码分别位于event.h和event-internal.h中 1.event: s ...
- libevent(五)event
libevent使用struct event来表示一个事件. #define evutil_socket_t int #define ev_uint8_t unsigned char #define ...
- 【传智播客】Libevent学习笔记(二):创建event_base
目录 00. 目录 01. 简介 02. 创建默认的event_base 03. 创建复杂的event_base 3.1 event_config_new函数 3.2 event_base_new_w ...
- libevent源码学习(15):信号event的处理
目录信号event处理流程与信号event相关的结构体初始化工作创建一个信号event添加一个信号event信号回调函数信号event的激活 Libevent中的event,主要分为三大类 ...
- 【转】libevent源码分析
libevent源码分析 转自:http://www.cnblogs.com/hustcat/archive/2010/08/31/1814022.html 这两天没事,看了一下Memcached和l ...
- libevent (二) 接收TCP连接
libevent 接收TCP连接 Evconnlistener 机制为您提供了侦听和接受传入的 TCP 连接的方法.下面的函数全部包含在`<event2/listener.h>`中. ev ...
随机推荐
- Hadoop学习记录(3)|HDFS API 操作|RPC调用
HDFS的API操作 URL方式访问 package hdfs; import java.io.IOException; import java.io.InputStream; import java ...
- linux文件权限查看及修改-chmod
查看linux文件的权限:ls -l 文件名称 查看linux文件夹的权限:ls -ld 文件夹名称(所在目录) 修改文件及文件夹权限: sudo chmod -(代表类型)×××(所有者)×××(组 ...
- python —print
今天开始学python了,“装X”安装了最新版本python 3.4.1 然后,print “hello world!" 就出错了... 一搜原来... python v3.0以后的版本pr ...
- 教程-Delphi资源文件(全面分析于使用)
Delphi资源文件(全面分析之位图.光标.图标.AVI.JPEG.Wave) 几乎每个Windows应用程序都使用图标.图片.光标等资源.资源是程序的一部分,但是它是不可执行代码.下面我们就详细 ...
- 说一说window.parent
<iframe>标签是很常用的,嵌在页面之中,可以做独立的加载和刷新.比如说,页面分左右或者上下结构,一般左侧和上侧是导航部分,右侧和下侧是目标页面的展示部分,只需要设置导航链接的targ ...
- java09 队列Queue与Deque
队列Queue与Deque. Enumeration Hashtable与Hashtable子类Properties(资源配置文件) 引用类型(强.软.弱.虚)与WeakHashMap Identit ...
- 文件和目录之chdir、fchdir和getcwd函数
每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点(不以斜杠开始的路径名为相对路径名).当用户登录到UNIX系统时,其当前工作目录通常是口令文件(/etc/passwd)中该用户登录项的第 ...
- 在不同平台上CocosDenshion所支持的音频格式
在大多数平台上,cocos2d-x调用不同的SDK API来播放背景音乐和音效.CocosDenshion在同一时间只能播放一首背景音乐,但是能同时播放多个音效. 背景音乐 Platform supp ...
- Android 自定义View修炼-仿QQ5.0 的侧滑菜单效果的实现
有一段时间没有写博客了,最近比较忙,没什么时间写,刚好今天有点时间, 我就分享下,侧滑菜单的实现原理,一般android侧滑的实现原理和步骤如下:(源码下载在下面最后给出哈) 1.使用ViewGrou ...
- uboot源码解析
实例:1.3.4版本at91sam系列 GPIO部分: 一.初始化: include\asm-arm\arch-at91sam9\gpio.h 1.同一引脚的复用设置 2.输入输出初始化寄存器 3.得 ...