【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(),那么事件循环将停止如果出现错误,那么事件循环将停止
在事件循环中,会监听是否存在活跃事件,若存在活跃事件,则调用事件对应的回调函数。
按照上面说的,如果想停止事件循环:
移除 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_gettimeofday_cached()(你的系统可能实现 gettimeofday() 为一个系统调用,故用下面函数可避免系统调用的开销):
// 获取到的时间为开始执行此轮事件回调函数的时间// 成功返回 0 失败返回负数int event_base_gettimeofday_cached(struct event_base *base, struct timeval *tv_out);
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 状态,否则该事件将会被删除(即该事件是一次性的)。
常用 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也许用得上,这是后话,需要用到的时候继续查资料学习!
- 【slighttpd】基于lighttpd架构的Server项目实战(2)—预备知识之libevent
- 【slighttpd】基于lighttpd架构的Server项目实战(6)—预备知识之Http
- 【slighttpd】基于lighttpd架构的Server项目实战(4)—简单的echo服务器
- 【slighttpd】基于lighttpd架构的Server项目实战(5)—TCP的TIME_WAIT状态
- 【slighttpd】基于lighttpd架构的Server项目实战(11)—C++的Name Mangling
- 【slighttpd】基于lighttpd架构的Server项目实战(1)—前言及设计思路
- 【slighttpd】基于lighttpd架构的Server项目实战(3)—Master&Worker模式
- 【slighttpd】基于lighttpd架构的Server项目实战(7)—http-parser
- 【slighttpd】基于lighttpd架构的Server项目实战(8)—状态机机制回顾
- 【slighttpd】基于lighttpd架构的Server项目实战(9)—状态机
- 【slighttpd】基于lighttpd架构的Server项目实战(10)—插件&动态库
- Binder机制之Server端—预备知识
- Binder机制之Server端—预备知识
- 学习Identity Server 4的预备知识
- 基于WCF的SOA架构项目实战
- 基于libevent的http server
- 基于Libevent的HTTP Server
- 基于Libevent的HTTP Server
- lightoj1150 - Ghosts!
- 挖Linux中的古老缩略语 etc rc bin tty
- Androidx学习笔记(47)--- 借助xUtils实现下载
- Cocos2d-x TextAtlas的使用
- Webpack打包React报错Unexcepted token <
- 【slighttpd】基于lighttpd架构的Server项目实战(2)—预备知识之libevent
- Androidx学习笔记(48)--- 创建Activity
- centos 中打字的小键盘不见了
- 【慕课笔记】U3 继承 第2节 JAVA中的方法重写
- JAVA WEB学习路线
- UITableViewCell的循环利用 - 在storyboard里
- 设计模式:策略模式
- HDU1242 rescue 【BFS+优先队列】
- Androidx学习笔记(49)--- Activity的跳转