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) 相对时间定时器,如果最近的定时器已经触发过了,则重新选出下一个最近的定时器,将其置于堆顶。

periodics_reify(loop) 绝对时间定时器   和timers处理差不多
idle_reify(loop) 
 
e) EV_INVOKE_PENDING 从loop的pending数组中依次调用各个watcher的回调函数,优先级从高到低
 
/* 遍历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;
}
}
f) 如果当前loop中还有被激活的watcher,并且loop_done ==0 并且启动ev_run(flags)的参数没有设置EVRUN_ONCE和EVRUN_NOWAIT,则继续从1开始。

libev I/O事件的更多相关文章

  1. libev+TCP服务器事件轮询实例demo

    #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <std ...

  2. libev学习(一)

    一.libev简介 Libev是一个事件循环:你注册感兴趣的特定事件(比如一个文件可以读取时或者发生超时时),它将管理这些事件源,将这些事件反馈给你的程序.为了实现这些,至少要在你的进程(或线程)中执 ...

  3. 使用 libevent 和 libev 提高网络应用性能——I/O模型演进变化史

    构建现代的服务器应用程序需要以某种方法同时接收数百.数千甚至数万个事件,无论它们是内部请求还是网络连接,都要有效地处理它们的操作. 有许多解决方案,但事件驱动也被广泛应用到网络编程中.并大规模部署在高 ...

  4. Libev学习笔记3

    设置完需要监听的事件之后,就开始event loop了.在Libev中,该工作由ev_run函数完成.它的大致流程如下: int ev_run (EV_P_ int flags) { do { /* ...

  5. libev学习笔记

    转 libev的使用--结合Socket编程 作者:cxy450019566 之前自己学过一些libev编程的基础,这次写压测刚好用上了,才算真正动手写了些东西,在这里做一些总结.写这篇文章是为了用浅 ...

  6. libev

    libev是一个**事件驱动库**,它需要循环探测事件是否发生,在Linux上实际是封装了epoll等系统调用. 其循环过程由ev_loop( )函数设置,循环体是ev_loop结构. //创建事件循 ...

  7. Socket网络编程--Libev库学习(3)

    这一小节继续讲解各个观察器(Watcher). 上一小节已经讲解了ev_io(IO可读可写观察器),ev_stat(文件属性变化观察器),ev_signal(信号处理观察器),ev_timer(定时器 ...

  8. libev 学习使用

    libev 简单的I/O库.  a high performance full featured event loop written in c libev 的大小也比 libevent 小得多并且自 ...

  9. Libev库学习

    Libev库学习 https://www.cnblogs.com/wunaozai/p/3950249.html Libev库学习(1)https://www.cnblogs.com/wunaozai ...

随机推荐

  1. C#与dotNET项目想要另存为一个新项目sln文件丢了怎么办

    如下图所示,我想要另存一个工程,把 V4.4整个的项目另存为V4.5,我可以把解决方案文件(.sln)改名字,但是我没法把文件夹改名字,改了打开sln就说找不到. 很简单的一个思路是反正sln是多余的 ...

  2. 利用opencv进行简易的拍照并处理照片

    今天用python写了一个调用摄像头拍照并对图片进行素描化或动漫化的小demo. 首先我的环境是:PyCharm+python3.8+opencv-python(4.4.0.42) 我们分析一下思路, ...

  3. SpringMVC注解搭配环境

    1.准备文件 2.工程中的pom <?xml version="1.0" encoding="UTF-8"?> <project xmlns= ...

  4. Cannot connect to runtime process

    发生一个或多个错误. 未能启动调试适配器.可以在输出窗口中查看额外的信息. Cannot connect to runtime process, timeout after 10000 ms (rea ...

  5. List集合与Set集合(ArrayList,LinkedList,Vector,HashSet,LinkedHashSet,可变参数)

    List集合介绍及常用方法 import java.util.ArrayList; import java.util.Iterator; import java.util.List; /* java. ...

  6. vue3 学习笔记(九)——script setup 语法糖用了才知道有多爽

    刚开始使用 script setup 语法糖的时候,编辑器会提示这是一个实验属性,要使用的话,需要固定 vue 版本. 在 6 月底,该提案被正式定稿,在 v3.1.3 的版本上,继续使用但仍会有实验 ...

  7. 联盛德 HLK-W806 (五): W801开发板上手报告

    目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...

  8. 洛谷 P5518 - [MtOI2019]幽灵乐团 / 莫比乌斯反演基础练习题(莫比乌斯反演+整除分块)

    洛谷题面传送门 一道究极恶心的毒瘤六合一题,式子推了我满满两面 A4 纸-- 首先我们可以将式子拆成: \[ans=\prod\limits_{i=1}^A\prod\limits_{j=1}^B\p ...

  9. 【Linux】tmux安装(非root)及其使用

    tmux(terminal multiplexer)是Linux上的终端复用神器. 1. 安装 (1)下载 下载及其依赖软件. wget -c https://github.com/tmux/tmux ...

  10. perl和python3 同时打开两个文件

    perl : while(defined(my $f1=<FQ1>) && defined(my $f2=<FQ2>)){ condition } python ...