(转)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 的方法):

  1. select
  2. poll
  3. epoll
  4. kqueue
  5. devpoll
  6. evport
  7. win32

在我们创建 event_base 的时候,Libevent 会为我们选择最快的后端。创建 event_base 通过函数 event_base_new() 来完成:

  1. // 创建成功返回一个拥有默认设置的 event base
  2. // 创建失败返回 NULL
  3. struct event_base *event_base_new(void);
  4. // 查看 event_base 实际上使用到的后端
  5. const char *event_base_get_method(const struct event_base *base);

对于大多数程序来说,默认设置已经够用了。

event_base 的释放使用函数:

  1. void event_base_free(struct event_base *base);

事件循环(event loop)

event_base 会持有一组 event(这是我们前面说到的),换而言之就是说,我们可以向 event_base 中注册 event(具体如何注册本文先不谈)。如果我们向 event_base 中注册了一些 event,那么就可以让 Libevent 开始事件循环了:

  1. // 指定一个 event_base 并开始事件循环
  2. // 此函数内部被实现为一个不断进行的循环
  3. // 此函数返回 0 表示成功退出
  4. // 此函数返回 -1 表示存在未处理的错误
  5. int event_base_dispatch(struct event_base *base);

默认的事件循环会在以下的情况停止(也就是 event_base_dispatch 会返回):

  1. 如果 base 中没有 event,那么事件循环将停止
  2. 调用 event_base_loopbreak(),那么事件循环将停止
  3. 调用 event_base_loopexit(),那么事件循环将停止
  4. 如果出现错误,那么事件循环将停止

事件循环会检测是否存在活跃事件(之前已经介绍过活跃事件这一术语:http://name5566.com/4190.html),若存在活跃事件,那么调用事件对应的回调函数。

停止事件循环的可以通过移除 event_base 中的 event 来实现。如果你希望在 event_base 中存在 event 的情况下停止事件循环,可以通过以下函数完成:

  1. // 这两个函数成功返回 0 失败返回 -1
  2. // 指定在 tv 时间后停止事件循环
  3. // 如果 tv == NULL 那么将无延时的停止事件循环
  4. int event_base_loopexit(struct event_base *base,
  5. const struct timeval *tv);
  6. // 立即停止事件循环(而不是无延时的停止)
  7. int event_base_loopbreak(struct event_base *base);

这里需要区别一下 event_base_loopexit(base, NULL) 和 event_base_loopbreak(base):

  1. event_base_loopexit(base, NULL) 如果当前正在为多个活跃事件调用回调函数,那么不会立即退出,而是等到所有的活跃事件的回调函数都执行完成后才退出事件循环
  2. event_base_loopbreak(base) 如果当前正在为多个活跃事件调用回调函数,那么当前正在调用的回调函数会被执行,然后马上退出事件循环,而并不处理其他的活跃事件了

有时候,我们需要在事件的回调函数中获取当前的时间,这时候你不需要调用 gettimeofday() 而是使用 event_base_gettimeofday_cached()(你的系统可能实现 gettimeofday() 为一个系统调用,你可以避免系统调用的开销):

  1. // 获取到的时间为开始执行此轮事件回调函数的时间
  2. // 成功返回 0 失败返回负数
  3. int event_base_gettimeofday_cached(struct event_base *base,
  4. struct timeval *tv_out);

如果我们需要(为了调试)获取被注册到 event_base 的所有的 event 和它们的状态,调用 event_base_dump_events() 函数:

  1. // f 表示输出内容的目标文件
  2. void event_base_dump_events(struct event_base *base, FILE *f);

event

现在开始详细的讨论一下 event。在 Libevent 中 event 表示了一组条件,例如:

  1. 一个文件描述符可读或者可写
  2. 超时
  3. 出现一个信号
  4. 用户触发了一个事件

event 的相关术语:

  1. 一个 event 通过 event_new() 创建出来,那么它是已初始化的,另外 event_assign() 也被用来初始化 event(但是有它特定的用法)
  2. 一个 event 被注册到(通过 add 函数添加到)一个 event_base 中,其为 pending 状态
  3. 如果 pending event 表示的条件被触发了,那么此 event 会变为活跃的,其为 active 状态,则 event 相关的回调函数被调用
  4. event 可以是 persistent(持久的)也可以是非 persistent 的。event 相关的回调函数被调用之后,只有 persistent event 会继续转为 pending 状态。对于非 persistent 的 event 你可以在 event 相关的事件回调函数中调用 event_add() 使得此 event 转为 pending 状态

event 常用 API:

  1. // 定义了各种条件
  2. // 超时
  3. #define EV_TIMEOUT 0x01
  4. // event 相关的文件描述符可以读了
  5. #define EV_READ 0x02
  6. // event 相关的文件描述符可以写了
  7. #define EV_WRITE 0x04
  8. // 被用于信号检测(详见下文)
  9. #define EV_SIGNAL 0x08
  10. // 用于指定 event 为 persistent
  11. #define EV_PERSIST 0x10
  12. // 用于指定 event 会被边缘触发(Edge-triggered 可参考 http://name5566.com/3818.html
  13. #define EV_ET 0x20
  14. // event 的回调函数
  15. // 参数 evutil_socket_t --- event 关联的文件描述符
  16. // 参数 short --- 当前发生的条件(也就是上面定义的条件)
  17. // 参数 void* --- 其为 event_new 函数中的 arg 参数
  18. typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
  19. // 创建 event
  20. // base --- 使用此 event 的 event_base
  21. // what --- 指定 event 关心的各种条件(也就是上面定义的条件)
  22. // fd --- 文件描述符
  23. // cb --- event 相关的回调函数
  24. // arg --- 用户自定义数据
  25. // 函数执行失败返回 NULL
  26. struct event *event_new(struct event_base *base, evutil_socket_t fd,
  27. short what, event_callback_fn cb,
  28. void *arg);
  29. // 释放 event(真正释放内存,对应 event_new 使用)
  30. // 可以用来释放由 event_new 分配的 event
  31. // 若 event 处于 pending 或者 active 状态释放也不会存在问题
  32. void event_free(struct event *event);
  33. // 清理 event(并不是真正释放内存)
  34. // 可用于已经初始化的、pending、active 的 event
  35. // 此函数会将 event 转换为非 pending、非 active 状态的
  36. // 函数返回 0 表示成功 -1 表示失败
  37. int event_del(struct event *event);
  38. // 用于向 event_base 中注册 event
  39. // tv 用于指定超时时间,为 NULL 表示无超时时间
  40. // 函数返回 0 表示成功 -1 表示失败
  41. int event_add(struct event *ev, const struct timeval *tv);

一个范例:

  1. #include <event2/event.h>
  2. // event 的回调函数
  3. void cb_func(evutil_socket_t fd, short what, void *arg)
  4. {
  5. const char *data = arg;
  6. printf("Got an event on socket %d:%s%s%s%s [%s]",
  7. (int) fd,
  8. (what&EV_TIMEOUT) ? " timeout" : "",
  9. (what&EV_READ) ? " read" : "",
  10. (what&EV_WRITE) ? " write" : "",
  11. (what&EV_SIGNAL) ? " signal" : "",
  12. data);
  13. }
  14. void main_loop(evutil_socket_t fd1, evutil_socket_t fd2)
  15. {
  16. struct event *ev1, *ev2;
  17. struct timeval five_seconds = {5, 0};
  18. // 创建 event_base
  19. struct event_base *base = event_base_new();
  20. // 这里假定 fd1、fd2 已经被设置好了(它们都被设置为非阻塞的)
  21. // 创建 event
  22. ev1 = event_new(base, fd1, EV_TIMEOUT | EV_READ | EV_PERSIST, cb_func,
  23. (char*)"Reading event");
  24. ev2 = event_new(base, fd2, EV_WRITE | EV_PERSIST, cb_func,
  25. (char*)"Writing event");
  26. // 注册 event 到 event_base
  27. event_add(ev1, &five_seconds);
  28. event_add(ev2, NULL);
  29. // 开始事件循环
  30. event_base_dispatch(base);
  31. }

Libevent 能够处理信号。信号 event 相关的函数:

  1. // base --- event_base
  2. // signum --- 信号,例如 SIGHUP
  3. // callback --- 信号出现时调用的回调函数
  4. // arg --- 用户自定义数据
  5. #define evsignal_new(base, signum, callback, arg) \
  6. event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
  7. // 将信号 event 注册到 event_base
  8. #define evsignal_add(ev, tv) \
  9. event_add((ev),(tv))
  10. // 清理信号 event
  11. #define evsignal_del(ev) \
  12. event_del(ev)

在通常的 POSIX 信号处理函数中,不少函数是不能被调用的(例如,不可重入的函数),但是在 Libevent 中却没有这些限制,因为信号 event 设定的回调函数运行在事件循环中。另外需要注意的是,在同一个进程中 Libevent 只能允许一个 event_base 监听信号。

性能相关问题

Libevent 提供了我们机制来重用 event 用以避免 event 在堆上的频繁分配和释放。相关的接口:

  1. // 此函数用于初始化 event(包括可以初始化栈上和静态存储区中的 event)
  2. // event_assign() 和 event_new() 除了 event 参数之外,使用了一样的参数
  3. // event 参数用于指定一个未初始化的且需要初始化的 event
  4. // 函数成功返回 0 失败返回 -1
  5. int event_assign(struct event *event, struct event_base *base,
  6. evutil_socket_t fd, short what,
  7. void (*callback)(evutil_socket_t, short, void *), void *arg);
  8. // 类似上面的函数,此函数被信号 event 使用
  9. #define evsignal_assign(event, base, signum, callback, arg) \
  10. 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() 实际上完成了两件事情:

  1. 通过内存分配函数在堆上分配了 event
  2. 使用 event_assign() 初始化了此 event

event_free() 实际上完成了两件事情:

    1. 调用 event_del() 进行 event 的清理工作
    2. 通过内存分配函数在堆上释放此 event
时间: 2024-10-12 20:09:50

(转)Libevent(2)— event、event_base的相关文章

[libevent]event,event_base结构体描述

libevent的核心-event Libevent是基于事件驱动(event-driven)的,从名字也可以看到event是整个库的核心.event就是Reactor框架中的事件处理程序组件:它提供了函数接口,供Reactor在事件发生时调用,以执行相应的事件处理,通常它会绑定一个有效的句柄. //在event2/event_struct.h中event的结构描述 struct event { TAILQ_ENTRY (event) ev_next; /*增加下一个事件*/ TAILQ_ENT

libevent(十一)event

libevent使用event表示一个事件,结构如下: struct event { TAILQ_ENTRY(event) ev_active_next; TAILQ_ENTRY(event) ev_next; /* for managing timeouts */ union { TAILQ_ENTRY(event) ev_next_with_common_timeout; int min_heap_idx; } ev_timeout_pos; evutil_socket_t ev_fd; s

libevent之event

就如libevent官网上所写的“libevent - an event notification library”,libevent就是一个基于事件通知机制的库,可以看出event是整个库的核心.event就是Reactor框架中的事件处理程序组件(event_handler),它提供了函数接口,供Reactor在事件发生时调用,以执行相应的事件处理,通常它会绑定一个有效的句柄. event结构体 event结构体定义在<event2/event_struct.h>中: 1 struct e

Libevent源码分析—event, event_base

event和event_base是libevent的两个核心结构体,分别是反应堆模式中的Event和Reactor.源码分别位于event.h和event-internal.h中 1.event: struct event { TAILQ_ENTRY (event) ev_next; //I/O事件 TAILQ_ENTRY (event) ev_active_next; //所有激活事件的链表 TAILQ_ENTRY (event) ev_signal_next; //Signal事件 //定时

libevent - an event notification library

libevent API 可以指定一个函数回调,当在文件描述符上产生了特定操作或者有超时动作时,这个函数回调会被调用,不仅如此,libevent还支持的事件类型包括信号量和常规超时. libevent的意图在于替代在事件驱动的网络服务中的事件循环,一个应用在使用libevent时之需要调用event_dispatch(),然后添加一些事件,而不需要噶便原来的事件循环. 就目前来说,libevent支持  /dev/poll, kqueue(2), event ports, POSIX selec

libevent源码分析-event

event结构 event相关接口 Libevent对event的管理 event结构 event是Reactor模式中的最重要的组件.它包含了了一个句柄fd,并设置监听这个句柄上的哪些事件(读/写等),设置了对应的函数指针,在事件到来时,回调函数指针来处理事件. 先看一下event的结构.它位于include/event2/event_struct.h中 struct event { TAILQ_ENTRY(event) ev_active_next; TAILQ_ENTRY(event) e

[libevent]Libevent介绍与编译

libevent介绍 libevent是一个轻量级的,开源高性能的,基于事件触发的网络库,适用于windows.linux.bsd等多种平台,内部使用select.epoll.kqueue等系统调用管理事件机制. 编译库代码,编译脚本会判断OS支持哪种类型的事件机制(select.epoll或kqueue),然后条件编译相应代码,供上层使用的接口仍然是保持统一的. 有许多开源项目使用libevent,例如memcached.使用libevent,使得memcached可以适应多种操作系统.Lib

libevent学习四

构建event_base 在你开始使用任何Libevent前,你需要先创建一个或多个event_base.每个event_base管理着一个event的集合,并可以检测出哪些event被激活了.如果event_base使用了锁,就可以在多线程中安全的访问它.但要注意它的主poll函数只能被单个线程运行.如果你想用多个线程运行IO迭代器,你需要为每个线程分配一个event_base. 注:在以后的版本中,Libevent可能提供对跨线程event的支持. 每个event_base都有一个"方法&q

轻量级网络库libevent概况

Libevent is a library for writing fast portable nonblocking IO. libevent是一个为编写快速可移植的非阻塞IO程序而设计的. libevent组件 libevent包括了以下组件: 1. evutil Generic functionality to abstract out the differences between different platforms' networking implementations. 用于抽象