IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 通读 libevent 的设计思路与实现细节 -> 正文阅读

[C++知识库]通读 libevent 的设计思路与实现细节

libevent 流程图

在这里插入图片描述

libevent 沉思录

1 libevent的设计思路

设计思路做到统一
统一事件类型:信号事件,定时事件,IO时间
统一系统调用:epoll/ select/ poll/ win 等作为底层 去实现不同的eventop(事件多路分发器)的接口

struct eventop 
{
int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
int (*dispatch)(struct event_base *, struct timeval *);
}

组织形式是reactor模式
反转即cb。每个文件描述符上的可读/可写事件可以创建多个事件处理器(不同的回调函数),libevent的IO事件队列将相同文件描述符的事件处理器组织在一起(event_io_map),当事件就绪时,可以根据fd找到对应的IO事件队列(evmap_io),将队列中的每一个节点(event)按照优先级插入到不同的激活队列中

struct epollop {
	struct epoll_event *events; // reactor(event_base)监听的所有的事件(事件数>文件描述符数)
	int nevents;
	int epfd;
};

struct evmap_io
{
	struct event_list events; // 文件描述符上所有的事件
	ev_uint16_t nread;
	ev_uint16_t nwrite;
}

struct event_base
{
	const struct eventop *evsel; // 多路复用的底层系统实现
	struct event_io_map io; // evmap_io[fd]    idx:fd  ?--> evmap_io fd到events的映射
struct event_list eventqueue; // 注册事件队列
struct event_list *activequeues; // 激活队列
struct event_changelist changelist; //事件变化队列。 用途:如果一个文件描述符上注册的事件被多次修改,则可以使用缓冲来避免重复的系统调用(比如epoll_ctl).

}

2 注册队列和激活队列的作用

epoll相比于poll在于返回的就绪队列,不需要再次遍历,返回的就是可处理的。
同理,激活队列也是相同作用,激活队列中的都是直接可以执行cb的。

event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
	switch (queue) {
	case EVLIST_INSERTED: // event_add()调用
		TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
		break;
	case EVLIST_ACTIVE:   // dispatch()调用
		base->event_count_active++;
		TAILQ_INSERT_TAIL(&base->activequeues[ev->ev_pri],ev,ev_active_next);
		break;
	case EVLIST_TIMEOUT: 
{
		if (is_common_timeout(&ev->ev_timeout, base)) {
			struct common_timeout_list *ctl =
			    get_common_timeout_list(base, &ev->ev_timeout);
			insert_common_timeout_inorder(ctl, ev);
		} else
			min_heap_push(&base->timeheap, ev);
		break;
	}
	}
}

3 event_add 为什么没有直接调用epoll_ctl

你先变,稳定了我在加
event_changelist_add() —> event_changelist_get_or_construct() 只修改event_changelist,因为文件描述符可能被多次设置监听读写时间,所以在这里只记录变化的,在dispatch时再epoll_apply_changes()。

//自上次调用 eventtop.dispatch 以来的“更改”列表。
struct event_changelist {
	struct event_change *changes; 
	int n_changes; // 改变的个数
	int changes_size;
};

4 libevent非内部函数是什么 | 是如何实现非阻塞的

在event_loop中

while(!done)
{
if (N_ACTIVE_CALLBACKS(base)) {
			int n = event_process_active(base);
			if ((flags & EVLOOP_ONCE)   // 如果所有激活事件都已经处理 | 
			    && N_ACTIVE_CALLBACKS(base) == 0
			    && n != 0)
				done = 1;
		} else if (flags & EVLOOP_NONBLOCK) // 非阻塞所有激活时间处理完毕就返回了
			done = 1;
}
/*
低优先级的可能会执行不到,被高优先级的激活事件队列饿死

激活事件队列同样有优先级,从高到低执行 对应激活事件队列中的每一个事件的回调cb

实际并没有把所有激活事件队列执行完毕,当队列中有一个非内部事件(用户自定义的)执行完毕就返回了,外层只要没有done(主循环大多数时候都是要在的),还会再下一次继续执行 
大多数时候返回的是我们处理的非内部事件的数量 为的是这个EVLOOP_ONCE参数,只执行一次event_loop
 */
static int
event_process_active(struct event_base *base)
{
	/* Caller must hold th_base_lock */
	struct event_list *activeq = NULL;
	int i, c = 0;

	for (i = 0; i < base->nactivequeues; ++i) {
		if (TAILQ_FIRST(&base->activequeues[i]) != NULL) {
			base->event_running_priority = i;
			activeq = &base->activequeues[i];
			c = event_process_active_single_queue(base, activeq);
			if (c < 0) {
				base->event_running_priority = -1;
				return -1;
			} else if (c > 0) // 
				break; /* Processed a real event; do not consider lower-priority events */
			
			/* If we get here, all of the events we processed
			 * were internal.  Continue. */
		}
	}

	event_process_deferred_callbacks(&base->defer_queue,&base->event_break); //执行延迟回调函数,当所有的激活事件都处理完成后,需要做的事情
	base->event_running_priority = -1;
	return c;
}

libevent 中的尾队列TAILQ结构

c语言实现的双向链表 queue.h 用来存储事件 list的原型
尾队列头tqh 和 尾队列元素tqe

在这里插入图片描述

  • 通过TAILQ_HEAD(name, type)宏可以快速的定义某一个类型结构体的队列.
    队列处理方面不需要知道队列中数据是什么。类似queue<your_type> 这种传入泛型。
  • 二级指针的用法
    有一个类型叫做指针的指针tqh_last,于是可以修改指针的指针存储的指针的地址,而不是修改指针存储的数据的地址。这样不用存储最后一个节点,而是最后一个节点的tqe_next指针
  • 插入/删除都是o(1)
/* 
 * Tail queue definitions. 尾队列定义 
 */  
#define TAILQ_HEAD(name, type)                      \  
struct name {                               \  
    struct type *tqh_first; /* first element */         \  
    struct type **tqh_last; /* addr of last next element */     \  二级指针
}  
  
#define TAILQ_HEAD_INITIALIZER(head)                    \  
    { NULL, &(head).tqh_first }  
  
#define TAILQ_ENTRY(type)                       \  
struct {                                \  
    struct type *tqe_next;  /* next element */          \  
    struct type **tqe_prev; /* address of previous next element */  \  
}  
  
/* 
 * tail queue access methods 
 */  
#define TAILQ_FIRST(head)       ((head)->tqh_first)  
#define TAILQ_END(head)         NULL  
#define TAILQ_NEXT(elm, field)      ((elm)->field.tqe_next)  
#define TAILQ_LAST(head, headname)                  \  
    (*(((struct headname *)((head)->tqh_last))->tqh_last))  
/* XXX */  
#define TAILQ_PREV(elm, headname, field)                \  
    (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))  
#define TAILQ_EMPTY(head)                       \  
    (TAILQ_FIRST(head) == TAILQ_END(head))  
  
#define TAILQ_FOREACH(var, head, field)                 \  
    for((var) = TAILQ_FIRST(head);                  \  
        (var) != TAILQ_END(head);                   \  
        (var) = TAILQ_NEXT(var, field))  
  
#define TAILQ_FOREACH_REVERSE(var, head, headname, field)       \  
    for((var) = TAILQ_LAST(head, headname);             \  
        (var) != TAILQ_END(head);                   \  
        (var) = TAILQ_PREV(var, headname, field))  
  
/* 
 * Tail queue functions. 
 */  
#define TAILQ_INIT(head) do {                       \  
    (head)->tqh_first = NULL;                    \  
    (head)->tqh_last = &(head)->tqh_first;                \  
} while (0)  
  
#define TAILQ_INSERT_HEAD(head, elm, field) do {            \  
    if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)  \  
        (head)->tqh_first->field.tqe_prev =           \  
            &(elm)->field.tqe_next;              \  
    else                                \  
        (head)->tqh_last = &(elm)->field.tqe_next;        \  
    (head)->tqh_first = (elm);                   \  
    (elm)->field.tqe_prev = &(head)->tqh_first;           \  
} while (0)  
  
#define TAILQ_INSERT_TAIL(head, elm, field) do {            \  
    (elm)->field.tqe_next = NULL;                    \  
    (elm)->field.tqe_prev = (head)->tqh_last;         \  
    *(head)->tqh_last = (elm);       \elm是一个指针, tqh_last是一个指针的指针 这里相当于操作tqe_next了
    (head)->tqh_last = &(elm)->field.tqe_next;            \  记录的是最后元素的下一个元素的地址,便于尾插
} while (0)  
  
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {      \  
    if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\  
        (elm)->field.tqe_next->field.tqe_prev =           \  
            &(elm)->field.tqe_next;              \  
    else                                \  
        (head)->tqh_last = &(elm)->field.tqe_next;        \  
    (listelm)->field.tqe_next = (elm);               \  
    (elm)->field.tqe_prev = &(listelm)->field.tqe_next;       \  
} while (0)  
  
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do {           \  
    (elm)->field.tqe_prev = (listelm)->field.tqe_prev;        \  
    (elm)->field.tqe_next = (listelm);               \  
    *(listelm)->field.tqe_prev = (elm);              \  
    (listelm)->field.tqe_prev = &(elm)->field.tqe_next;       \  
} while (0)  
  
#define TAILQ_REMOVE(head, elm, field) do {             \  
    if (((elm)->field.tqe_next) != NULL)             \  
        (elm)->field.tqe_next->field.tqe_prev =           \  
            (elm)->field.tqe_prev;               \  
    else                                \  
        (head)->tqh_last = (elm)->field.tqe_prev;     \  
    *(elm)->field.tqe_prev = (elm)->field.tqe_next;           \  
} while (0)  
  
#define TAILQ_REPLACE(head, elm, elm2, field) do {          \  
    if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \  
        (elm2)->field.tqe_next->field.tqe_prev =      \  
            &(elm2)->field.tqe_next;             \  
    else                                \  
        (head)->tqh_last = &(elm2)->field.tqe_next;       \  
    (elm2)->field.tqe_prev = (elm)->field.tqe_prev;           \  
    *(elm2)->field.tqe_prev = (elm2);                \  
} while (0)  
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-26 11:24:29  更:2022-04-26 11:28:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 21:49:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码