libev I/O事件
libev是来实现reactor模式,主要包含三大部分:
1. watcher:watcher是Reactor中的Event Handler。
作用:1)向事件循环提供了统一的调用接口(按类型区分)
2)它是外部代码的注入口,维护着具体的watcher信息,如:绑定的回调函数,watcher的优先级,是否激活等。
定义位置:在ev.h中可以看到各种watcher的定义,如:ev_io, ev_timer等
2.ev_loop:充当一个Reactor的角色,是事件循环的上下文环境,像树干一样串起了各个叶子watcher。
定义位置:1)ev_loop定义在ev.c文件中
2)ev_vars.h定义了ev_loop的各种属性
3)ev_wrap.h定义了与loop相关的各种宏
watcher的管理:在ev_loop中watcher按各自类型进行分类存储
3.ev_run:执行事件的循环引擎,即reactor的select方法,是树的主体部分。
作用:1)通过像ev_run传入一个ev_loop实例,便可以开启一个事件循环
2)ev_run实际为do-while循环,在循环期间检查loop中注册的各种watcher事件。若事件就绪,就会触发相应的watcher,循环持续直至遇到ev_break或没有就绪的watcher
对于libev中的I/O事件的触发来说,也遵循上述所说。
1.I/O事件的watcher定义
typedef struct ev_io
{
EV_WATCHER_LIST (ev_io) int fd; //文件描述符
int events; //事件类型
} ev_io;
ev_io是一种具体的watcher,它有两个自己专有的成员变量fd和events。在ev_io中有部分参数是从EV_WATCHER_LIST (ev_io)继承而来
#define EV_WATCHER_LIST(type)
EV_WATCHER (type)
struct ev_watcher_list *next;
#define EV_WATCHER(type) \
int active; //active 表示当前watcher是否被激活。ev_TYPE_start调用后置位,ev_TYPE_stop调用后复位
int pending;//表示当前watcher有事件就绪,等待处理。pending的值其实就是当前watcher在pendings队列中的下标;
EV_DECL_PRIORITY //是当前watcher的优先级;
EV_COMMON // 在前面有定义为 *data;data: 附加数据指针,用来在watcher中携带额外所需的数据;
EV_CB_DECLARE (type)//cb:是事件触发后的回调函数定义。
代码定义中嵌套了一些基类和其他一些宏定义,这里直接写出来,方便理解。ev_io的watcher的完整参数如下:
typedef struct ev_io
{
int active;
int pending;
int priority;
void *data;
void (*cb)(struct ev_loop *loop, struct ev_io *w, int revents);
struct ev_watcher_list *next; int fd; /* 这里的fd,events就是派生类的私有成员,分别表示监听的文件fd和触发的事件(可读还是可写) */
int events;
} ev_io;
2、ev_loop
struct ev_loop
{
ev_tstamp ev_rt_now;//其中ev_tstamp 就是时间单位,实际上就是double类型#define ev_rt_now ((loop)->ev_rt_now)
// 这里decl是declare的意思,ev_vars.h 里面会定义一堆的变量,这些变量
// 都是本结构的成员,ev_vars.h展开的时候会用到下面这一行VAR的宏
#define VAR(name,decl) decl;
#include "ev_vars.h"
#undef VAR
};
ev_vars.h中包含很多关键的成员,如:
epoll相关的成员变量
#if EV_USE_EPOLL || EV_GENWRAP
VARx(struct epoll_event *, epoll_events) // 相当于struct epoll_event *epoll_events
VARx(int, epoll_eventmax) ////目前epoll_events数组的大小,可以扩充,每次以2倍的大小扩充
VARx(int *, epoll_eperms)
VARx(int, epoll_epermcnt)
VARx(int, epoll_epermmax)
#endif
VARx(int, backend_fd)// 对于epoll来说,就是epoll使用的fd
VARx(ev_tstamp, backend_mintime) /* assumed typical timer resolution */
//对于epoll来说,实际的函数是ev_epoll.c中的epoll_modify函数,这个函数会执行epoll_ctl
VAR (backend_modify, void (*backend_modify)(EV_P_ int fd, int oev, int nev))
//对于epoll来说,实际的函数是ev_poll.c中的epoll_poll函数,这个函数会执行epoll_wait
VAR (backend_poll , void (*backend_poll)(EV_P_ ev_tstamp timeout))
fd相关的成员变量
VARx(ANFD *, anfds)//这个数组是以fd为索引
VARx(int, anfdmax)//anfd 数组的大小 VARx(int *, fdchanges)// fdchangemax大小的数组,每个元素是一个fd,这个数组中存了所有epoll需要poll的fd
VARx(int, fdchangemax)//数组的容量
VARx(int, fdchangecnt)// 数组中实际的元素的大小
ANFD和fd一一对应,结构体ANFD如下(在ev.c中进行定义)
typedef struct
{
WL head;//关注同一个fd的事件的watcher的链表,一个fd可以有多个watcher监听它的事件
unsigned char events; /* 所监视的事件*/
unsigned char reify; /* 标志位,用来标记ANFD需要被重新实例化(EV_ANFD_REIFY, EV__IOFDSET) */
unsigned char emask; /* the epoll backend stores the actual kernel mask in here */
unsigned char unused;
#if EV_USE_EPOLL
unsigned int egen; /* generation counter to counter epoll bugs */
#endif
#if EV_SELECT_IS_WINSOCKET || EV_USE_IOCP
SOCKET handle;
#endif
#if EV_USE_IOCP
OVERLAPPED or, ow;
#endif
} ANFD;
ANFD表示事件循环中对一个文件描述符fd的监视的基本信息结构体
3 ev_run
在ev_run之前还有有如下几个需要关注的步骤
1)IO事件的初始化和设置(在ev.h中进行定义)
#define ev_io_init(ev,cb,fd,events) do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)//初始化
#define ev_io_set(ev,fd_,events_) do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0)//设置
2) ev_io_start:每当有新的IO监视器fd加入,调用wlist_add()添加到anfds[fd]的链表head中。
void noinline
ev_io_start (EV_P_ ev_io *w) EV_THROW//# define EV_P struct ev_loop *loop # define EV_P_ EV_P,
{
int fd = w->fd; if (expect_false (ev_is_active (w)))
return; assert (("libev: ev_io_start called with negative fd", fd >= 0));
assert (("libev: ev_io_start called with illegal event mask", !(w->events & ~(EV__IOFDSET | EV_READ | EV_WRITE)))); EV_FREQUENT_CHECK; ev_start (EV_A_ (W)w, 1);//将参数w表示的watcher激活(w->active=1)
array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);
wlist_add (&anfds[fd].head, (WL)w);//将watcher w 加入到 w所关注的fd在anfds[](loop中)中相应的位置处的结构体ANFD中的watcher list链表中。 /* common bug, apparently */
assert (("libev: ev_io_start called with corrupted watcher", ((WL)w)->next != (WL)w)); fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);//将w所关注的fd加入到int *fdchanges数组中。
w->events &= ~EV__IOFDSET; EV_FREQUENT_CHECK;
}
3)fd_change:如果一个anfds的元素监控条件发生改变,如何修改这个元素的监控条件呢。anfds的下标可以用fd来表示,这里有一个新的数组,数组元素内容是新添加的要监视的IO事件的fd或者修改监视内容的fd,数组名是fdchanges,也是动态数组。这个数组记录了新加入fd或者修改的fd的值,具体实现函数为“fd_change”
inline_size void
fd_change (EV_P_ int fd, int flags)
{
unsigned char reify = anfds [fd].reify;
anfds [fd].reify |= flags;//标志,表示fd监视条件被修改了 if (expect_true (!reify))//如果fd最初的监视条件为空,表示新加入的fd
{
++fdchangecnt;//fd计数器加一
array_needsize (int, fdchanges, fdchangemax, fdchangecnt, EMPTY2);//添加到fdchanges数组中
fdchanges [fdchangecnt - 1] = fd;
}
//如果不是新加入的fd,则fdchanges数组中已经有fd了。表示以前添加过对fd的IO监视
}
3) ev_run
a.调用“fd_reify”:遍历fdchanges数组,如果发现fd的监视条件发生变化了,就会调用epoll_ctl()函数来改变fd的监视状态。
fdchanges数组记录了anfds数组中的watcher监控条件可能被修改的文件描述符,并在适当的时候将调用系统的epoll_ctl或则其他文件复用机制修改系统监控的条件。
inline_size void
fd_reify (EV_P)
{
int i;
for (i = 0; i < fdchangecnt; ++i)
{
int fd = fdchanges [i]; //遍历取出可能改变监控条件的fd
ANFD *anfd = anfds + fd;//得到anfds中下标
ev_io *w;//定义一个ev_io指针 unsigned char o_events = anfd->events;
unsigned char o_reify = anfd->reify; anfd->reify = 0; /*if (expect_true (o_reify & EV_ANFD_REIFY)) probably a deoptimisation */
{
anfd->events = 0; for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next)
//获得fd全部的新的监控事件集合,存放在events成员变量中
anfd->events |= (unsigned char)w->events; if (o_events != anfd->events)//如果新监控事件和旧监控事件不同,
o_reify = EV__IOFDSET; /* actually |= *///修改标志位,表示fd监控条件改变
} if (o_reify & EV__IOFDSET)//fd监控条件改变,调用backend_modify也就是epoll_ctl()修改fd的监控条件
backend_modify (EV_A_ fd, o_events, anfd->events);
} fdchangecnt = 0;//一次遍历完成,fdchanges数组个数清零
}
b)time_update 更新时间,校准时间(后续再补充)
c) 调用backend_poll(loop, waittime):对于使用epoll的系统来说,它实际上是调用ev_epoll.c 中的epoll_poll()函数,执行wait 操作:
eventcnt = epoll_wait (backend_fd, epoll_events, epoll_eventmax, timeout * 1e3);
成功的话,返回了响应事件的个数,然后执行了fd_event()(ev_epoll.c文件调用,在ev.c中定义函数)
inline_speed void
fd_event (EV_P_ int fd, int revents)
{
ANFD *anfd = anfds + fd;
// ! 逻辑反操作
if (expect_true (!anfd->reify))////reify是0,取反即为1 走进if条件里面
/*如果reify是0,则表示我们添加了新的事件在fd上*/
fd_event_nocheck (EV_A_ fd, revents);
}
fd_event_nocheck:
inline_speed void
fd_event_nocheck (EV_P_ int fd, int revents)
{
ANFD *anfd = anfds + fd;
ev_io *w; for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next)//对fd上的监视器依次做检测,
{
int ev = w->events & revents;//相应的事件被触发了 if (ev)//pending条件满足,监控器加入到pendings数组中pendings[pri]上的pendings[pri][old_lenght+1]的位置上
ev_feed_event (EV_A_ (W)w, ev);
}
}
ev_feed_event
void noinline
ev_feed_event (EV_P_ void *w, int revents) EV_THROW
{
W w_ = (W)w;
int pri = ABSPRI (w_); if (expect_false (w_->pending))
pendings [pri][w_->pending - 1].events |= revents;
else
{
w_->pending = ++pendingcnt [pri];
array_needsize (ANPENDING, pendings [pri], pendingmax [pri], w_->pending, EMPTY2);
pendings [pri][w_->pending - 1].w = w_;
pendings [pri][w_->pending - 1].events = revents;
} pendingpri = NUMPRI - 1;
}
d)time_update 再次更新校准时间
timers_reify(loop) 相对时间定时器,如果最近的定时器已经触发过了,则重新选出下一个最近的定时器,将其置于堆顶。
/* 遍历pendings二维数组,执行事件触发回调函数 */
void noinline
ev_invoke_pending (EV_P)
{
pendingpri = NUMPRI;//#define NUMPRI (EV_MAXPRI - EV_MINPRI + 1) while (pendingpri) /* pendingpri possibly gets modified in the inner loop 外层while循环是按优先级递减*/
{
--pendingpri; while (pendingcnt [pendingpri])
//内层while循环同一优先级的ANPENDING按数组顺序进行遍历,遍历到的每一个ANPENDING都对应一个watcher,得到watcher之后就执行该watcher对应的回调函数。
{
ANPENDING *p = pendings [pendingpri] + --pendingcnt [pendingpri];//这个是在干吗,触发回调函数了吗 p->w->pending = 0;//pending的值其实就是当前watcher在pendings队列中的下标,这里将pending值置0即为优先级放到最低
EV_CB_INVOKE (p->w, p->events);
EV_FREQUENT_CHECK;
}
}
libev I/O事件的更多相关文章
- libev+TCP服务器事件轮询实例demo
#include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <std ...
- libev学习(一)
一.libev简介 Libev是一个事件循环:你注册感兴趣的特定事件(比如一个文件可以读取时或者发生超时时),它将管理这些事件源,将这些事件反馈给你的程序.为了实现这些,至少要在你的进程(或线程)中执 ...
- 使用 libevent 和 libev 提高网络应用性能——I/O模型演进变化史
构建现代的服务器应用程序需要以某种方法同时接收数百.数千甚至数万个事件,无论它们是内部请求还是网络连接,都要有效地处理它们的操作. 有许多解决方案,但事件驱动也被广泛应用到网络编程中.并大规模部署在高 ...
- Libev学习笔记3
设置完需要监听的事件之后,就开始event loop了.在Libev中,该工作由ev_run函数完成.它的大致流程如下: int ev_run (EV_P_ int flags) { do { /* ...
- libev学习笔记
转 libev的使用--结合Socket编程 作者:cxy450019566 之前自己学过一些libev编程的基础,这次写压测刚好用上了,才算真正动手写了些东西,在这里做一些总结.写这篇文章是为了用浅 ...
- libev
libev是一个**事件驱动库**,它需要循环探测事件是否发生,在Linux上实际是封装了epoll等系统调用. 其循环过程由ev_loop( )函数设置,循环体是ev_loop结构. //创建事件循 ...
- Socket网络编程--Libev库学习(3)
这一小节继续讲解各个观察器(Watcher). 上一小节已经讲解了ev_io(IO可读可写观察器),ev_stat(文件属性变化观察器),ev_signal(信号处理观察器),ev_timer(定时器 ...
- libev 学习使用
libev 简单的I/O库. a high performance full featured event loop written in c libev 的大小也比 libevent 小得多并且自 ...
- Libev库学习
Libev库学习 https://www.cnblogs.com/wunaozai/p/3950249.html Libev库学习(1)https://www.cnblogs.com/wunaozai ...
随机推荐
- 大一C语言学习笔记(9)---指针篇--从”内存的使用“和“流程控制”的角度来理解“指针变量的使用‘
#深入理解指针变量 举个错误栗子: //以下代码的目的是输出100和1000,但输出结果只有一个100 #include<stdio.h> #include<malloc.h> ...
- 《Python语言程序设计》【第2周】Python基本图形绘制
实例2:Python蟒蛇绘制 #PythonDraw.py import turtle #import 引入了一个绘图库 turtle 海龟库--最小单位像素 turtle.setup(650, 35 ...
- Json跨域登录问题的之Access-Control-Allow-Origin 站点跨域请求的问题
跨域调用json问题 闲暇之时,做了一个博客站点,站点发布网络之后程序功能完成,最后发现了一个跨域的问题,比如我使用abc.com打开系统,一切正常,后台没有任何文件请求报错问题,然后我又使用了www ...
- 大爽Python入门教程 0-1 安装python
大爽Python入门公开课教案 点击查看教程总目录 一 如何找到下载地址并下载 下面展示找到下载地址的方法步骤 嫌步骤太慢可直接跳到第4步, 查看详细下载地址 使用搜索引擎搜索python 打开搜索结 ...
- 5.0jemter(英文版)录制脚本,进行压力测试
压力测试的目的:找到瓶颈.优化速率 1.jemter,Test Plan-->>Add-->>Threds(users)-->>Thred Group创建线程组 2 ...
- 如何提高C# StringBuilder的性能
本文探讨使用C# StringBuilder 的最佳实践,用于减少内存分配,提高字符串操作的性能. 在 .NET 中,字符串是不可变的类型.每当你在 .NET 中修改一个字符串对象时,就会在内存中创建 ...
- 提升AI智能化水平,打造智慧新体验
内容来源:华为开发者大会2021 HMS Core 6 AI技术论坛,主题演讲<提升AI智能化水平,打造智慧新体验>. 演讲嘉宾:沈波,华为消费者AI与智慧全场景ML Kit产品总监 今天 ...
- System.Web.Optimization
项目中引用了 System.Web.Optimization 这个程序集,缺少程序集会报错: 命名空间"System.Web"中不存在类型或命名空间名"Optimizat ...
- pycahrm下载
下载地址: https://www.jetbrains.com/pycharm/download/#section=windows 下载社区版本,不用破解,可以直接使用
- 8.1 k8s使用PV/PVC做数据持久化运行redis服务,数据保存至NFS
1.制作redis docker镜像 1.1 准备alpine基础镜像 # 下载 docker pull alpine:3.13 # 更改tag docker tag alpine:3.13 192. ...