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

来源:互联网 发布:淘宝商品详情图做法 编辑:程序博客网 时间:2024/06/05 19:18

简介

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

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

Linux 下的 epoll()BSD 下的 kqueue()Solaris 下的 evportsWindows 下的 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 的方法):

selectpollepollkqueuedevpollevportwin32

在创建 event_base 时,libevent 会为我们选择最快的后端。

创建 event_base 的函数:

// 创建成功返回一个拥有默认设置的 event base// 创建失败返回 NULLstruct 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 用于保存 socketev_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相关

// 用于关闭一个 socketint 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 为非阻塞的 socketint evutil_make_socket_nonblocking(evutil_socket_t sock);// 设置 sock 的地址可重用int evutil_make_listen_socket_reuseable(evutil_socket_t sock);

字符串相关

// 它们对应于标准的 snprintf 和 vsnprintfint 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 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 入户中山没有三年居住证明怎么办 加拼关单号舱单信息没有怎么办 外贸中交货期晚了怎么办 履约保函到期了怎么办 续贷高校未通过怎么办 安卓手机网速慢怎么办 探探性别错了怎么办 尿酸高导致脚肿怎么办 联通光猫复位后怎么办 头部和脸部出油怎么办 vcd解码板坏了怎么办 脚崴了里面筋疼怎么办 长寿龟身上烂了怎么办 纱裙没有腰身显胖怎么办 橘子平台下载速度慢怎么办 换手机号了支付宝账号怎么办 怀孕期间脚扭了怎么办 买了临街房后悔怎么办 绝地求生困在废车里怎么办 身体出现辅助睡眠状态怎么办 扁桃体发炎喉咙痒咳嗽怎么办 电脑玩英雄杀卡怎么办 康佳冰箱显示2e怎么办 高考登陆系统登录密码错误怎么办 苹果遇到ad登录问题怎么办? 小孩2天不拉屎怎么办 20天宝宝不大便怎么办 小孩几天不拉大便怎么办 宝宝经常不拉大便怎么办 宝宝便秘 肛裂了怎么办 3个月宝宝不便便怎么办 宝宝8个月不便便怎么办 宝宝在幼儿园不爱说话怎么办 广东学考考了d怎么办 上嘴唇干燥紧绷怎么办 美国大学gpa非常低怎么办 大学手机作弊通报教务处怎么办 崩坏3邮箱验证码过期怎么办 快递员被顾客恶意投诉怎么办 5岁幼儿逻辑思维差怎么办 脸一边胖一边瘦怎么办