Libevent代码初期了解

  1. I/O框架库概述
    1. Reactor模式
    2. Libevent源码分析

学习来源
<Linux 高性能服务器编程>

I/O框架库概述

Reactor模式


句柄(Handler)
由于统一了事件源, 一个事件一般跟一个句柄绑定在一起, 事件就绪之后 会通过句柄通知这一个事件.
在Linux中 I/O 事件对应的句柄->文件描述符, 信号事件->信号值

事件多路分发器(EventDemultiplexer)
事件的到来是随机的, 异步的. 所以只能通过一个循环一直等待事件并进行处理 — 事件循环
一般通过IO复用实现 select poll epoll_wait

事件处理器(EventHandle)
具体事件处理器(ConcreteEventHandler)
事件处理器执行事件对应的业务逻辑, 通常包含一个或多个handler_event回调函数, 这些回调函数在事件循环中被执行

Reactor
handler_events: 执行事件循环 重复等待事件, 然后依次调用对应的事件处理器
register_handler: 向事件多路分发器中注册事件
remove_handler: 从中删除一个事件

Libevent源码分析

#include <sys/signal.h>
#include <event.h>
#include <cstdio>

void signal_cb(int fd, short event, void* argc)
{
event_base* base = (event_base*)argc;
timeval delay = {2, 0};

printf("Caught an interrupt signal\n");
event_base_loopexit(base, &delay);
}

void timeout_cb(int fd, short event, void* argc)
{
printf("timeout\n");
}
int main()
{
// 相当于创建一个Reactor实例
event_base* base = event_init();

event* signal_event = evsignal_new(base, SIGINT, signal_cb, base);
event_add(signal_event, nullptr);

event* timeout_event = evtimer_new(base, timeout_cb, nullptr);
timeval tv{1, 0};
event_add(timeout_event, &tv);

event_base_dispatch(base);
event_free(signal_event);
event_free(timeout_event);
event_base_free(base);
}

创建一个事件处理器 然后为绑定上相应的回调函数.
然后把这个事件处理器注册到事件队列中中,

然后事件多路分发器依靠循环一直等待事件的到来, 事件到来后通知相应的事件处理器
Reactor则管理这些

首先要去了解下 事件处理器 对应的就是event这个结构体

struct event {
struct event_callback ev_evcallback;

// 事件处理器从属的 event_base
struct event_base *ev_base;
// 信号值 或者 文件描述符
evutil_socket_t ev_fd;
// 定时器的超时时间
struct timeval ev_timeout;

// 仅用于定时事件
union {
// 队列--指出在通用定时器中的位置
TAILQ_ENTRY(event) ev_next_with_common_timeout;
// 时间堆--指出了在时间堆中的位置
int min_heap_idx;
} ev_timeout_pos;

union {
struct {
// 通过这个成员 将具有相同文件描述符的IO事件处理器串联起来
LIST_ENTRY (event) ev_io_next;
struct timeval ev_timeout;
} ev_io;
struct {
// 相同信号的串联起来
LIST_ENTRY (event) ev_signal_next;
short ev_ncalls;
/* Allows deletes in callback */
short *ev_pncalls;
} ev_signal;
} ev_;

// 事件类型, 可以通过位处理设置非互斥事件
short ev_events;
// 当前激活事件的类型, 说明被激活的原因
short ev_res;
};

可以看到其中有很多的属性, 三种事件对应的不同的属性.

这些属性的填充函数
evsignal_new``evtimer_new是宏 统一调用event_new
event_new调用event_assign来进行主要的填充

//@通过宏封装注册函数

一个事件生成函数 经过宏的封装(可以自动填充某些此事件用不到的参数)可以更方便的对应不同事件的生成, 既统一了注册, 又方便用户调用

属性之一便是回调函数, 事件回调函数有自己的规定

//@统一事件回调函数

void (*callback)(evutil_socket_t, short, void *)
这样能够统一回调函数的格式, 同时方便管理

事件处理器创建完毕, 该把事件处理器添加到事件注册队列. 样例代码中通过的event_add函数来实现将事件处理器添加到事件注册队列
event_add实际由event_add_nolock_实现 所以接下来是event_add_nolock_函数的说明

//@事件处理器的分发实现
将传入的event按照不同类型的事件处理器 分别处理
(因为event_new已经填充了ev_events说明事件类型)

IO事件 添加绑定
信号事件 绑定相应的信号
定时器 放入相关的的时间管理数据结构中

使用event_queue_insert_inserted进行注册
这里的代码2.1.11 与书上的差别较大, 少了多一半的功能, 也没有被抽成函数, 暂不知道对应的功能代码去了哪里
照书上来说event_queue_insert_inserted实现的是将事件处理器加入到event_base的某个事件队列中. 对于新添加的IO和信号事件处理器, 还需要让事件多路分发器来监听对应的事件, 然后建立相应的映射关系. 分别使用evmap_io_add_evmap_signal_add_(相当于图中的register_event)建立映射.

evmap_io_add_中有一个结构体event_io_map
event_io_map会根据不同的平台最终对应不同的数据结构

evmap_io_add_函数
函数中用到的东西, 我目前吸收不了……. 总之是为将IO事件处理器加入到event_base的事件队列中实现的

eventop结构体 是event_base中封装IO复用机制的结构体, 提供了统一的接口

// 为给定的event_base声明后端的结构体
struct eventop {
// 后端IO复用技术的名称
const char *name;

// 初始化 函数需要初始化所有要用的属性
// 返回的指针会被event_init存储在event_base.evbase
// 失败后返回NULL
void *(*init)(struct event_base *);
// 注册事件
int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
// 删除事件
int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
// 等待事件
int (*dispatch)(struct event_base *, struct timeval *);
// 释放IO复用机制使用的资源
void (*dealloc)(struct event_base *);
// 标记fork后是否需要重新初始化event_base的标志位
int need_reinit;
// 用于设定io复用技术支持的一些特性
enum event_method_feature features;
// 额外内存的分配
size_t fdinfo_len;
};

event_base是Libevent的Reactor. 超长结构体 删除了我不理解的部分

struct event_base {
// 记录选择的I/O复用机制
const struct eventop *evsel;
// 指向IO复用机制真正存储的数据
void *evbase;

// 事件变换队列 如果一个文件描述符上注册的事件被多次修改, 则可以使用缓冲避免重复的系统调用
// 比如epoll_ctl, 仅能用于时间复杂度O(1)的IO复用技术
struct event_changelist changelist;

// 信号的后端处理机制
const struct eventop *evsigsel;
// 信号事件处理器使用的数据结构, 其中封装了socketpair创建的管道. 用于信号处理函数和
// 事件多路分发器之间的通信, 统一事件源的思路
struct evsig_info sig;

// 添加到event_base的虚拟(所有, 激活)事件数量, 虚拟(所有, 激活)事件最大数量
int virtual_event_count;
int virtual_event_count_max;
int event_count;
int event_count_max;
int event_count_active;
int event_count_active_max;

// 处理完事件后 是否退出循环
int event_gotterm;
// 是否立即终止循环
int event_break;
// 是否启动一个新的事件循环
int event_continue;

// 当前正在处理的活动事件队列的优先级
int event_running_priority;

// 标记事件循环是否已经启动, 防止重入
int running_loop;

// 活动事件队列数组. 索引值越小的队列优先级越高. 高优先级的活动事件队列中的事件处理器被优先处理
struct evcallback_list *activequeues;
// 活动事件队列数组的大小 说明有nactivequeues个不同优先级的活动事件队列
int nactivequeues;
/** A list of event_callbacks that should become active the next time
* we process events, but not this time. */
struct evcallback_list active_later_queue;

// 共同超时逻辑

// 管理通用定时器队列 实体数量 总数
struct common_timeout_list **common_timeout_queues;
int n_common_timeouts;
int n_common_timeouts_allocated;

// 文件描述符和IO事件之间的映射关系表
struct event_io_map io;
// 信号值和信号事件之间的映射关系表
struct event_signal_map sigmap;
// 时间堆
struct min_heap timeheap;

// 管理系统时间的成员
struct timeval tv_cache;
struct evutil_monotonic_timer monotonic_timer;
struct timeval tv_clock_diff;
time_t last_updated_clock_diff;

#ifndef EVENT__DISABLE_THREAD_SUPPORT
// 多线程支持

// 当前运行该event_base的事件循环的线程
unsigned long th_owner_id;
// 独占锁
void *th_base_lock;
// 当前事件循环正在执行哪个事件处理器的回调函数
void *current_event_cond;
// 等待的线程数
int current_event_waiters;
#endif
// 正在处理的事件处理器的回调函数
struct event_callback *current_event;
};

事件循环, libevent的动力, 即事件循环

这次读了一下helloworld的 这一部分代码

static void
listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sa, int socklen, void *user_data)
{
struct event_base *base = user_data;
struct bufferevent *bev;

bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
fprintf(stderr, "Error constructing bufferevent!");
event_base_loopbreak(base);
return;
}
// 设置新创建的bufferevent的 读写回调函数 启动写事件 禁用读事件
bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
bufferevent_enable(bev, EV_WRITE);
bufferevent_disable(bev, EV_READ);

bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}

bufferevent_socket_new 创建了读和写event 设置了真实的回调函数bufferevent_writecb等 然后
bufferevent_enable是真正的事件注册函数, 将上面生成的event注册进入eventbase
EV_WRITE对应的epoll事件是EPOLLOUT
bufferevent_write将要写出的数据放入到缓冲区中。 一旦epollwait返回了 就去调用bufferevent_writecb将缓冲区
的内容写出, 然后同时调用用户设置的回调函数

2020年3月7日20:26:02
这次又读了下 系统是怎么区分不同事件来调用回调函数的, 结果返现框架将系统的事件类型对应成自己的事件类型

if (what & (EPOLLHUP|EPOLLERR)) {
ev = EV_READ | EV_WRITE;
} else {
if (what & EPOLLIN)
ev |= EV_READ;
if (what & EPOLLOUT)
ev |= EV_WRITE;
if (what & EPOLLRDHUP)
ev |= EV_CLOSED;
}

然后传入evmap_io_active_函数, 进而将ev保存到ev_res中然后直接作为参数 传给了回调函数


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。