转载地址:https://blog.csdn.net/jiange_zh/article/details/50631393

简介

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

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

  1. Linux 下的 epoll()
  2. BSD 下的 kqueue()
  3. Solaris 下的 evports
  4. 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也许用得上,这是后话,需要用到的时候继续查资料学习!

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

  1. 【slighttpd】基于lighttpd架构的Server项目实战(8)—状态机机制回顾

    转载地址:https://blog.csdn.net/jiange_zh/article/details/50640270 有限状态机FSM(Finite State Machine) 关于状态机的一 ...

  2. 【slighttpd】基于lighttpd架构的Server项目实战(3)—MasterWorker模式

    转载地址:https://blog.csdn.net/jiange_zh/article/details/50636180 现在,我们开始一步步构建我们的项目了- Master-Worker模式 本次 ...

  3. 【slighttpd】基于lighttpd架构的Server项目实战(7)—http-parser

    转载地址:https://blog.csdn.net/jiange_zh/article/details/50639178 对于http服务器,http request的解析是比较麻烦的,由于我们的重 ...

  4. 【slighttpd】基于lighttpd架构的Server项目实战(5)—TCP的TIME_WAIT状态

    转载地址:https://blog.csdn.net/jiange_zh/article/details/50637549 上一节我们已经开发了一个简单的echo服务器,在这里我们先不急着继续下去,先 ...

  5. 【slighttpd】基于lighttpd架构的Server项目实战(4)—简单的echo服务器

    转载地址:https://blog.csdn.net/jiange_zh/article/details/50636536 在这一部分,我们将对上一篇中的master-worker进行拓展,成为一个简 ...

  6. 【项目】基于SaaS的餐掌柜项目实战 阶段一 基于SaaS的餐掌柜项目实战 第1章 基础架构搭建 1 餐掌柜需求分析

    [项目]基于SaaS的餐掌柜项目实战 文章目录 [项目]基于SaaS的餐掌柜项目实战 阶段一 基于SaaS的餐掌柜项目实战 第1章 基础架构搭建 1 餐掌柜需求分析 1.1 餐饮行业分析 1.2 餐饮 ...

  7. 【项目】bxg基于SaaS的餐掌柜项目实战(2023)

    基于SaaS的餐掌柜项目实战 餐掌柜是一款基于SaaS思想打造的餐饮系统,采用分布式系统架构进行多服务研发,共包含4个子系统,分别为平台运营端.管家端(门店).收银端.小程序端,为餐饮商家打造一站式餐 ...

  8. 【PCIE】基于Riffa架构的PCIE项目

    基于Riffa架构的PCIE项目 Pcie分为四层: ① 物理层:完成信号的转换以及编码 包含 PMA 和 PCS PMA: Physical Media Attachment 物理媒介层,完成并转串 ...

  9. 简单谈一谈基于SaaS的餐掌柜项目实战

    基于SaaS的餐掌柜项目实战 由于基于SaaS的餐掌柜项目本身是一个较为复杂的系统,代码量也相对较大,因此这里只提供一段简单的代码作为示例: // 定义菜品列表 const menuList = [ ...

最新文章

  1. usaco shuttle puzzle(dfs剪枝)
  2. InetAddress类的一些方法
  3. 怎样用c语言写一个系统,用C语言写关于操作系统的一个问题。
  4. Python-Day3-数据类型
  5. hbase的集群搭建
  6. [有限元] Ansys Workbench 19.2 平面应力问题分析示例
  7. 数据竞赛入门-金融风控(贷款违约预测)三、特征工程
  8. oracle级联查询 level,ORACLE 数据库的级联查询 一句sql搞定(部门多级)
  9. c# oldb连接_C# 连接数据库(ODBC、Oledb、Oracle)
  10. Qt_解决Qt5.15 + Xcode12iOS端qmake不可用的问题
  11. Linux 日志查看常用命令
  12. 天轰穿·甜老丝儿。科创少年
  13. ACM退役帖(青岛赛后再更新)
  14. 孙式无极桩站桩要领--林泰年
  15. 变形金刚2影院版完整字幕
  16. linux中date命令详解,linux中date命令的详细解释
  17. 学会洞察行业,让选择大于努力
  18. 芜湖市计算机语言,2012年芜湖市小学计算机水平等级测评试卷(logo语言版)
  19. 神经网络训练算法的调用,人工神经网络训练案例
  20. 7. 常见网络攻击欺骗手段与防护

热门文章

  1. 00_设计模式6大原则
  2. RxJava学习-使用篇
  3. Java对象在Java虚拟机的创建过程
  4. 数据结构——绪论以及线性表的顺序表示
  5. 10行代码AC——1016 部分A+B (15分)
  6. 最全!最完整的求first集和follow的代码!!!编译原理 FIRST集和FOLLOW集的求法 代码,程序,实验报告
  7. led大屏按实际尺寸设计画面_年会活动要用LED大屏还是投影?专业行家都是看这些数据。...
  8. 传统公司部署OpenStack(t版)简易介绍(九)——控制台部署
  9. python笔记之序列(dict的基本使用和常用操作)
  10. 教室工资管理系统c语言课程设计csdn,工资管理系统(C编写)