【slighttpd】基于lighttpd架构的Server项目实战(2)—预备知识之libevent

简介

由于本项目是纯异步的,而对于大量 socket 连接,使用 select 并不高效。(参见我的另一篇博文:epoll简介

事实上,大部分系统提供了处理大量 socket 连接的解决方案:

Linux 下的 epoll()
BSD 下的 kqueue()
Solaris 下的 evports
Windows 下的 IOCP

由于各个平台使用了不同的接口,所以想编写跨平台的高性能异步程序就需要做一层跨平台封装。此时 Libevent 是一个不错的选择(当然你也可以自己来实现这个事-。-),其最底层 API(event 和 event_base API)为各个平台实现高性能异步程序提供了一致的接口。

一些概念

event:会绑定文件描述符、回调函数并表示一个或者多个条件(例如,文件描述符可以读、写或者超时等)。event 表示的条件如果被触发了,那么 event 会变为活跃的,它绑定的回调函数就会被执行。

event_base: 持有一组 event 并进行事件循环,event_base 会存在一个后端(也叫做方法),常见的后端包括 epoll、kqueue 等。

结构

组件:

evutil 用于抽象不同的平台的网络(基础的)实现;

event、event_base 为 Libevent 的核心,为不同的平台下基于事件的非阻塞 I/O 提供了一套抽象的接口;

bufferevent 对 Libevent 的基于事件的核心的封装。应用程序的读写请求是基于缓冲区的;

evbuffer 为 bufferevent 实现的缓冲区;

evhttp 一个简单的 HTTP client/server 的实现;

evdns 一个简单的 DNS client/server 的实现;

evrpc 一个简单的 RPC 实现;

库:

libevent_core 包括 util、event_base、evbuffer、bufferevent;

libevent_extra 包括 HTTP、DNS、RPC;

libevent 此库由于历史原因而存在,不要使用它;

libevent_pthreads 此库为基于 pthread 的线程和锁的实现;

libevent_openssl 此库通过 openssl 和 bufferevent 提供了加密通讯;

头文件:
所有的公用头文件位于 event2 目录中。

创建和销毁 event_base

event_base 是最基本的,故需要首先被创建。

event_base 结构持有一个 event 集合。如果 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
// 创建失败返回 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,那么就可以让 libevent 开始事件循环了:

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

event_base_dispatch会在以下情况停止:

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

在事件循环中,会监听是否存在活跃事件,若存在活跃事件,则调用事件对应的回调函数。

按照上面说的,如果想停止事件循环:

  1. 移除 event_base 中的 event。

  2. 如果想在 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_gettimeofday_cached()(你的系统可能实现 gettimeofday() 为一个系统调用,故用下面函数可避免系统调用的开销):

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

event事件

event是一组触发条件,例如:

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

相关术语:

一个 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 状态,否则该事件将会被删除(即该事件是一次性的)。

常用 API:

    // 定义了各种条件
    // 超时
    #define EV_TIMEOUT      0x01
    // 文件描述符可读
    #define EV_READ         0x02
    // 文件描述符可写
    #define EV_WRITE        0x04
    // 用于信号检测
    #define EV_SIGNAL       0x08
    // 用于指定 event 为 persistent
    #define EV_PERSIST      0x10
    // 用于指定 event 会被边缘触发
    #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);

信号 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)

注意,一个进程中 Libevent 只能允许一个 event_base 监听信号。

重用 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
使用 event_assign() 初始化了此 event

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

调用 event_del() 进行 event 的清理工作
通过内存分配函数在堆上释放此 event

常用基本数据类型

evutil_socket_t 用于保存 socket
ev_uint64_t 取值范围 [0, EV_UINT64_MAX]
ev_int64_t 取值范围 [EV_INT64_MIN, EV_INT64_MAX]
ev_uint32_t 取值范围 [0, EV_UINT32_MAX]
ev_int32_t 取值范围 [EV_INT32_MIN, EV_INT32_MAX]
ev_uint16_t 取值范围 [0, EV_UINT16_MAX]
ev_int16_t 取值范围 [EV_INT16_MIN, EV_INT16_MAX]
ev_uint8_t 取值范围 [0, EV_UINT8_MAX]
ev_int8_t 取值范围 [EV_INT8_MIN, EV_INT8_MAX]
ev_ssize_type(signed size_t)取值范围 [EV_SSIZE_MIN, EV_SSIZE_MAX]

时间相关

// 用于加或者减前两个参数,结果被保存在第三个参数中
#define evutil_timeradd(tvp, uvp, vvp) /* ... */
#define evutil_timersub(tvp, uvp, vvp) /* ... */

// 清除 timeval 将其值设置为 0
#define evutil_timerclear(tvp) /* ... */
// 判断 timeval 是否为 0,如果是 0 返回 false,否则返回 true
#define evutil_timerisset(tvp) /* ... */

// 比较两个 timeval
// 使用的时候这样用:
// evutil_timercmp(t1, t2, <=) 含义为判断 t1 <= t2 是否成立
// cmp 为所有的 C 关系操作符
#define evutil_timercmp(tvp, uvp, cmp)

// 获取当前时间并保存到 tv
// tz 目前无用
int evutil_gettimeofday(struct timeval *tv, struct timezone *tz);

Socket相关

// 用于关闭一个 socket
int evutil_closesocket(evutil_socket_t s);
#define EVUTIL_CLOSESOCKET(s) evutil_closesocket(s)

// 返回当前线程的最后一次 socket 操作的错误码
#define EVUTIL_SOCKET_ERROR()
// 改变当前 socket 的错误码
#define EVUTIL_SET_SOCKET_ERROR(errcode)
// 返回特定的 sock 的错误码
#define evutil_socket_geterror(sock)
// 通过 socket 错误码获取到一个字符串描述
#define evutil_socket_error_to_string(errcode)

// 设置 sock 为非阻塞的 socket
int evutil_make_socket_nonblocking(evutil_socket_t sock);

// 设置 sock 的地址可重用
int evutil_make_listen_socket_reuseable(evutil_socket_t sock);

字符串相关

// 它们对应于标准的 snprintf 和 vsnprintf
int evutil_snprintf(char *buf, size_t buflen, const char *format, ...);
int evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap);

结语

以上是libevent的基本介绍,在本次的项目中,会使用到最基本的event以及信号event。

对于数据、缓冲区的管理,bufferevent也许用得上,这是后话,需要用到的时候继续查资料学习!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值