博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【转载】使用事件模型 & libev学习
阅读量:5917 次
发布时间:2019-06-19

本文共 14511 字,大约阅读时间需要 48 分钟。

参考这篇文章:

这里面使用的是 libev ,不是libevent

Nodejs就是采用libev作为底层库。

 

先要进行安装,找到了这篇文章:

搜索了很多关于如何学网络编程的博客和问答。大致都是推荐学一个网络库,至于C++网络库有那么几个,各有各的好处。 这里就选这个代码量少了,方便入门,等有一定的基础后,再看看“学之者生,用之着死”的ace或者有可能成为C++标准网络库的boost::asio,这个都是后话了。

 

libev原地址国外的,访问不了。从csdn上下载了一份(),要1分的,不过我下载过了,再下载就不用了。

拷贝到03机器上,解压。

因为libev是C写的,估计用C++的编译器也没啥用。所以看看默认的编译器行不行。

./configure --prefix=/home/work/data/installed/libevmakemake install能够看到安装信息:----------------------------------------------------------------------Libraries have been installed in:   /home/work/data/installed/libev/libIf you ever happen to want to link against installed librariesin a given directory, LIBDIR, you must either use libtool, andspecify the full pathname of the library, or use the `-LLIBDIR'flag during linking and do at least one of the following:   - add LIBDIR to the `LD_LIBRARY_PATH' environment variable     during execution   - add LIBDIR to the `LD_RUN_PATH' environment variable     during linking   - use the `-Wl,-rpath -Wl,LIBDIR' linker flag   - have your system administrator add LIBDIR to `/etc/ld.so.conf'See any operating system documentation about shared libraries formore information, such as the ld(1) and ld.so(8) manual pages.---------------------------------------------------------------------- /bin/mkdir -p '/home/work/data/installed/libev/include' /usr/bin/install -c -m 644 ev.h ev++.h event.h '/home/work/data/installed/libev/include' /bin/mkdir -p '/home/work/data/installed/libev/share/man/man3' /usr/bin/install -c -m 644 ev.3 '/home/work/data/installed/libev/share/man/man3'make[1]: Leaving directory `/home/work/data/installed/libev-4.15'

 

然后在代码目录/home/work/data/code/libev_demo里写了个示例程序libev_demo.c

#include 
#include
//ev库头文件,注意这是C头文件,C++是ev++.h//定义一个ev_TYPE 的结构体ev_io stdin_watcher; //定义一个stdin的观测者ev_timer timeout_watcher; //定义一个timeout的观测者//所有的watcher的回调函数都有相似的特点//当stdin有可读的数据时,将会调用下面这个回调函数static void stdin_cb(EV_P_ ev_io *w, int revents) { puts("stdin ready"); //每一次调用都必须用对应的停止函数,手动的停止其watcher //应该是表示处理过了 ev_io_stop(EV_A_ w); //这将导致所有嵌套执行的ev_run停止监听 ev_break(EV_A_ EVBREAK_ALL); }//这是一个回调函数,用于定时器回调static void timeout_cb(EV_P_ ev_timer *w, int revents) { puts("timeout"); //这将导致最早运行的ev_run停止监听 ev_break(EV_A_ EVBREAK_ONE);}int main(int argc, char **args) { //使用一般默认的事件循环 struct ev_loop *loop = EV_DEFAULT; //初始化一个I/O watcher,然后启动它 ev_io_init(&stdin_watcher, stdin_cb, 0, EV_READ); ev_io_start(loop, &stdin_watcher); //初始化一个定时器watcher,然后启动它 //只有一次,没有重复的5.5秒定时 ev_timer_init(&timeout_watcher, timeout_cb, 5.5, 0); ev_timer_start(loop, &timeout_watcher); //这里开始loop,等待时间开始计时 ev_run(loop, 0); return 0;}

然后写个Makefile

CXX=gccINCPATH= \        /home/work/data/installed/libev/includeDEP_LDFLAGS= \        -L/home/work/data/installed/libev/libDEP_LDLIBS= \        -levTARGET= libev_demoall : $(TARGET)libev_demo : *.c        $(CXX) -o $@ $^ -I$(INCPATH) $(DEP_LDFLAGS) $(DEP_LDLIBS).PHONY : all cleanclean :        rm -rf $(TARGET)

然后编译并运行:

$ makegcc -o libev_demo libev_demo.c -I/home/work/data/installed/libev/include -L/home/work/data/installed/libev/lib -lev$ ./libev_demo timeout$ ./libev_demo lsstdin ready$ lslibev_demo  libev_demo.c  Makefile

运行程序后,在超时或者输入字符时会中断退出。并且字符会继续传递到命令行

 

另外,以上是C程序,文件后缀不能是cxx。对于cxx,要用C++的头文件。

下面在相同的目录里面写了一个C++的程序 libev_demo.cxx

#include 
#include
#include
using namespace std;class IOWatcher {public: IOWatcher(int fd, unsigned int events) { m_io.set(fd, events); m_io.set
(this); m_io.start(); } void CallBack(ev::io &w, int revents) { cout << "In IOWatcher::CallBack" << endl; w.stop(); }private: ev::io m_io;};int main() { ev::default_loop loop; IOWatcher my_io(STDIN_FILENO, ev::READ); loop.run(0); return 0;}

然后Makefile也要改一下,包括依赖的文件和所使用的编译器:

#CXX=gccCXX=/opt/compiler/gcc-4.8.2/bin/g++INCPATH= \        /home/work/data/installed/libev/includeDEP_LDFLAGS= \        -L/home/work/data/installed/libev/libDEP_LDLIBS= \        -lev -Wl,-rpath -Wl,LIBDIRTARGET= libev_demoall : $(TARGET)#libev_demo : *.clibev_demo : *.cxx        $(CXX) -o $@ $^ -I$(INCPATH) $(DEP_LDFLAGS) $(DEP_LDLIBS).PHONY : all cleanclean :        rm -rf $(TARGET)

编译并运行:

$ make cleanrm -rf libev_demo$ make/opt/compiler/gcc-4.8.2/bin/g++ -o libev_demo libev_demo.cxx -I/home/work/data/installed/libev/include -L/home/work/data/installed/libev/lib -lev -Wl,-rpath -Wl,LIBDIR$ ./libev_demo lsIn IOWatcher::CallBack$ lslibev_demo  libev_demo.c  libev_demo.cxx  Makefile

可以看到,C++的程序也能够正确编译和执行。

 

用gcc -E选项来编译前面的C程序,可以看到预处理的,带有宏展开的程序

gcc -E libev_demo.c -I/home/work/data/installed/libev/include > tmp

然后原来的c文件,展开成了2271行的文件

其中main函数部分:

int main(int argc, char **args) { struct ev_loop *loop = ev_default_loop (0); do { do { ((ev_watcher *)(void *)((&stdin_watcher)))->active = ((ev_watcher *)(void *)((&stdin_watcher)))->pending = 0; ( (ev_watcher *)(void *)(((&stdin_watcher))))->priority = (0); (((&stdin_watcher)))->cb = ((stdin_cb)); } while (0); do { ((&stdin_watcher))->fd = ((0)); ((&stdin_watcher))->events = ((EV_READ)) | EV__IOFDSET; } while (0); } while (0); ev_io_start(loop, &stdin_watcher); do { do { ((ev_watcher *)(void *)((&timeout_watcher)))->active = ((ev_watcher *)(void *)((&timeout_watcher)))->pending = 0; ( (ev_watcher *)(void *)(((&timeout_watcher))))->priority = (0); (((&timeout_watcher)))->cb = ((timeout_cb)); } while (0); do { ((ev_watcher_time *)((&timeout_watcher)))->at = ((5.5)); ((&timeout_watcher))->repeat = ((0)); } while (0); } while (0); ev_timer_start(loop, &timeout_watcher); ev_run(loop, 0); return 0;}

Libev通过一个struct ev_loop结构表示一个事件驱动的框架。

在这个框架里面通过ev_xxx结构,ev_init、ev_xxx_set、ev_xxx_start接口箱这个事件驱动的框架里面注册事件监控器,当相应的事件监控器的事件出现时,便会触发该事件监控器的处理逻辑,去处理该事件。处理完之后,这些监控器进入到下一轮的监控中。

符合一个标准的事件驱动状态的模型。

Libev 除了提供了基本的三大类事件(IO事件、定时器事件、信号事件)外还提供了周期事件、子进程事件、文件状态改变事件等多个事件,这里我们用三大基本事件写一个例子。

#include 
#include
#include
#include
#include
void io_action(struct ev_loop *main_loop, ev_io *io_w, int e) { int rst; char buf[1024]; memset(buf, 0, sizeof(buf)); puts("In IO action"); read(STDIN_FILENO, buf, sizeof(buf)); buf[1023] = '\0'; printf("String input: %s\n", buf); //ev_io_stop(main_loop, io_w); // 不注释的话,只会监测一遍IO输入}void timer_action(struct ev_loop *main_loop, ev_timer *time_w, int e) { puts("In Time action"); ev_timer_stop(main_loop, time_w);}void signal_action(struct ev_loop *main_loop, ev_signal *signal_w, int e) { puts("In Signal action"); ev_signal_stop(main_loop, signal_w); ev_break(main_loop, EVBREAK_ALL);}int main(int argc, char **argv) { ev_io io_w; ev_timer timer_w; ev_signal signal_w; struct ev_loop *main_loop = ev_default_loop(0); ev_init(&io_w, io_action); ev_io_set(&io_w, STDIN_FILENO, EV_READ); ev_init(&timer_w, timer_action); // #define ev_timer_set(ev, after_, repeat_) ev_timer_set(&timer_w, 2, 0); ev_init(&signal_w, signal_action); // Ctrl+C ev_signal_set(&signal_w, SIGINT); ev_io_start(main_loop, &io_w); ev_timer_start(main_loop, &timer_w); ev_signal_start(main_loop, &signal_w); ev_run(main_loop, 0);}

其中主要的时间线循环如下:

使用libev的核心是事件循环,可以用 ev_default_loop 或 ev_loop_new 函数创建循环,或者直接使用 EV_DEFAULT 宏,区别是 ev_default_loop 创建的事件循环不是线程安全的,而 ev_loop_new 创建的事件循环不能捕捉信号和子进程的观察器。大多数情况下,可以像下面这样使用:1 if (!ev_default_loop (0))2   fatal ("could not initialise libev, bad $LIBEV_FLAGS in environment?");或者明确选择一个后端:1 struct ev_loop *epoller = ev_loop_new (EVBACKEND_EPOLL | EVFLAG_NOENV);2 if (!epoller)3   fatal ("no epoll found here, maybe it hides under your chair");如果需要动态分配循环的话,建议使用 ev_loop_new 和 ev_loop_destroy 。在创建子进程后,且想要使用事件循环时,需要先在子进程中调用 ev_default_fork 或 ev_loop_fork 来重新初始化后端的内核状态,它们分别对应 ev_default_loop 和 ev_loop_new 来使用。ev_run 启动事件循环。它的第二个参数为0时,将持续运行并处理循环直到没有活动的事件观察器或者调用了 ev_break 。 另外两个取值是 EVRUN_NOWAIT 和 EVRUN_ONCE 。ev_break 跳出事件循环(在全部已发生的事件处理完之后)。 第二个参数为 EVBREAK_ONE 或 EVBREAK_ALL 来指定跳出最内层的 ev_run 或者全部嵌套的 ev_run 。ev_suspend 和 ev_resume 用来暂停和重启事件循环,比如在程序挂起的时候。
ev_run是libev的核心,他主要做了五件事情:1.更新更改的FD事件2.进行必要的sleep3.backend_poll收集pending的IO事件4.收集pending的timer事件5.调用所有pending的事件

以上示例程序中,如果注释掉"ev_break(main_loop, EVBREAK_ALL);",那么程序会在调用三个回调函数后才会结束(所有的ev_xxx_stop都被调用了),否则一直监听着。也就是说,只要三种事件都发生,三个ev_xxx_stop都被调用了,那么就会出了event_loop循环。

 

具体ev_run和ev_break的参数说明如下:

void ev_run (EV_P_ int flags);void ev_break (EV_P_ int how);同样我们这里比较关注flags和how这两个参数。flags有下面这几个:0:默认值。一直循环进行处理,直到外部引用计数==0或者是显示退出。EVRUN_NOWAIT:运行一次,poll时候不会等待。如果有pending事件进行处理,否则立即返回。EVRUN_ONCE:运行一次,poll时候会等待至少一个event发生,处理完成之后返回。而how有下面这几个:EVBREAK_ONE:只是退出一次ev_run这个调用。通常来说使用这个就可以了。EVBREAK_ALL:退出所有的ev_run调用。这种情况存在于ev_run在pengding处理时候会递归调用。

使用libev的核心是事件循环,可以用 ev_default_loop 或 ev_loop_new 函数创建循环,或者直接使用 EV_DEFAULT 宏,区别是 ev_default_loop 创建的事件循环不是线程安全的,而 ev_loop_new 创建的事件循环不能捕捉信号和子进程的观察器

 

各个观察器(watcher)

ev_io  获取标准输入

ev_timer  创建一个 x 秒之后启动的计时器

ev_periodic 创建一个小时为单位的周期定时器

static void clock_cb (struct ev_loop *loop, ev_periodic *w, int revents){    // ... its now a full hour (UTC, or TAI or whatever your clock follows)}ev_periodic hourly_tick;ev_periodic_init (&hourly_tick, clock_cb, 0., 3600., 0);ev_periodic_start (loop, &hourly_tick); 或者自定义周期方式:#include 
static ev_tstamp my_scheduler_cb (ev_periodic *w, ev_tstamp now){ return now + (3600. - fmod (now, 3600.));}ev_periodic_init (&hourly_tick, clock_cb, 0., 0., my_scheduler_cb); 从当前时间开始:
ev_periodic_init (&hourly_tick, clock_cb, fmod (ev_now (loop), 3600.), 3600., 0);
ev_periodic_start (loop, &hourly_tick);

示例代码如下:

#include 
#include
#include
#include
static void periodic_callback(struct ev_loop *loop,ev_periodic * w, int revents){ printf("every 3 seconds\n"); //ev_break(loop,EVBREAK_ALL);}//ev_tstamp=doublestatic ev_tstamp periodic_scheduler_callback(ev_periodic *w,ev_tstamp now){ return now+3; //注意时间要加上个now,是一个绝对时间}int main(int argc, char **args){ struct ev_loop * main_loop=ev_default_loop(0); ev_periodic periodic_watcher; //下面这个是第3个参数为3 是一个表达式 ev_init(&periodic_watcher, periodic_callback); ev_periodic_set(&periodic_watcher,0,3,0); ev_periodic_start(main_loop,&periodic_watcher); ev_run(main_loop,0); //如果时间周期计算方式,不能通过一个表达式来表示,那么可以通过一个函数来表示,放在set的第4个参数 ev_init(&periodic_watcher,periodic_callback); ev_periodic_set(&periodic_watcher,0,0,periodic_scheduler_callback); ev_periodic_start(main_loop,&periodic_watcher); ev_run(main_loop,0); //注意上下两部分是等价的,注释掉一个就可以看到相同的效果 return 0;}

 

ev_signal 在处理信号

 

ev_child   fork 一个新进程,给它安装一个child处理器等待进程结束。

写一个程序来实验一下。开始怎么都写不对,看了该系列的下一篇以为因为没有set。其实不是,是因为没有ev_run(main_loop, 0);()

#include 
#include
#include
#include
#include
#include
#include
static void child_cb (EV_P_ ev_child *w, int revents){ ev_child_stop (EV_A_ w); printf ("process %d exited with status %x\n", w->rpid, w->rstatus);}int main(int argc, char** argv) { ev_child cw; struct ev_loop *main_loop = ev_default_loop(0); pid_t pid = fork (); if (pid < 0) { // error } else if (pid == 0) { // the forked child executes here puts("I am here sleeping"); sleep(5); // seconds puts("I am waking up"); //exit (1); } else { puts("F am here sleeping"); sleep(1); // let child go first puts("F am waking up"); ev_child_init (&cw, child_cb, pid, 0); printf("F get, child: %d\n", pid); //ev_child_set(&cw, pid, 0); //ev_child_start (EV_DEFAULT_ &cw); ev_child_start(main_loop, &cw); ev_run(main_loop, 0); } return 0;}

其中有一些细节。如果father在child之前醒来,比如上面的程序,那么表现正常

$ ./libev_demo F am here sleepingI am here sleepingF am waking upF get, child: 37255I am waking upprocess 37255 exited with status 0

如果child在father之前醒来,那么程序表现是:father立刻会醒,并且进行处理

这种现象,与子进程是否调用了exit无关。而且即使子进程不sleep,直接返回,父进程的sleep也无效,直接返回。总之,只要子进程退出了,父进程就会立即进行处理。

 

ev_stat 文件状态观察器 

示例代码如下:

#include 
#include
#include
#include
#include
static void stat_callback(struct ev_loop *loop,ev_stat *w, int revents){ printf("I'm here\n"); if(w->attr.st_nlink) { printf("The file size %ld\n",(long)w->attr.st_size); } else { printf("No file here\n"); }}int main(int argc, char **args){ struct ev_loop *main_loop=ev_default_loop(0); ev_stat stat_watcher; ev_init(&stat_watcher,stat_callback); ev_stat_set(&stat_watcher,"/home/work/data/code/libev_demo4/tmp",0); ev_stat_start(main_loop,&stat_watcher); ev_run(main_loop,0); return 0;}

运行之后,在这个目录创建文件,或者删除文件,或者修改文件,都会触发到事件(虽然创建文件的触发,可能有一点延时,不清楚是不是轮询):

$ ./libev_demo I'm hereThe file size 0I'm hereNo file hereI'm hereThe file size 5I'm hereNo file hereI'm hereThe file size 0

可以有如下的这些属性:

if(w->attr.st_nlink)    {        printf("The file st_dev %d\n",w->attr.st_dev);        printf("The file st_ino %d\n",w->attr.st_ino);        printf("The file st_mode %d\n",w->attr.st_mode);        printf("The file st_nlink %d\n",w->attr.st_nlink);        printf("The file st_uid %d\n",w->attr.st_uid);        printf("The file st_gid %d\n",w->attr.st_gid);        printf("The file st_rdev %d\n",w->attr.st_rdev);        printf("The file st_size %d\n",w->attr.st_size);        printf("The file st_atime %d\n",w->attr.st_atime);        printf("The file st_mtime %d\n",w->attr.st_mtime);        printf("The file st_ctime %d\n",w->attr.st_ctime);    }    else    {        printf("文件不存在\n");    }

 

上面了解了几个最主要的watcher了。ev_io ev_timer ev_periodic ev_signal ev_child ev_stat,

除了上面的几个外,还有下面这几个 ev_idle ev_prepare/ev_check ev_embed ev_fork ev_cleanup ev_async . 功能如下:

ev_fork(创建的进程时的观察器), ev_async(异步调用观察器), ev_cleanup(event loop退出时触发事件), ev_prepare(每次event loop之前事件), ev_check(每次event loop之后事件), ev_idle(每次event loop空闲触发事件).

 

其中ev_timer 和 ev_periodic 的区别在于 periodic是周期性的。虽然ev_timer有一个参数repeat,但是它的含义不是重复多少次,而是下一次按照多少间隔来处理,如下:

ev_timer_set(&timer_w, 5, 1);这样的话,第一次等5秒,然后后面每次等待1秒,就会触发事件。而如果在事件处理函数里面调用了ev_timer_stop(main_loop, time_w);这样的话,就只会处理第一次的5秒,后面的1秒都不会处理了。

ev_periodic如再上面的示例程序,可以每隔多长时间循环处理;也可以自定义时间,来进行周期性处理。

 

至此,原文的系列基本学习完了。更多的内容,后面再学习。(原文地址:)

(完)

 

转载地址:http://znfvx.baihongyu.com/

你可能感兴趣的文章
《SEM长尾搜索营销策略解密》一一2.10 小领域文化进入红利期
查看>>
想卸载 IE?微软:那也得先升级 IE11!
查看>>
程序员,你的职业不要固步自封
查看>>
ShenmeGUI —— Ruby 使用 HTML 构建 GUI
查看>>
本月推荐:15 个有用的 JavaScript 和 CSS 库
查看>>
微软免费之路开启,大象跳舞的两个难题
查看>>
《Node.js区块链开发》一3.4 DPoS:授权股权证明机制
查看>>
Centos下安装svn客户端的命令
查看>>
《机器人爱好者(第3辑)》——早期的DARPA大奖赛
查看>>
《精通LabVIEW虚拟仪器程序设计与案例实现》一2.11 思考与练习
查看>>
虚拟现实和现实增强技术带来的威胁
查看>>
【PMP认证考试之个人总结】第 8 章 人力资源管理
查看>>
《JavaScript构建Web和ArcGIS Server应用实战》——2.7 总结
查看>>
《Photoshop混合模式深度剖析》—第1章如何学好混合模式
查看>>
《网站情感化设计与内容策略》一10.6 规划内容管理
查看>>
如何在 Ubuntu 中管理开机启动应用
查看>>
《Java遗传算法编程》—— 2.5 轮盘赌选择
查看>>
《统计会犯错——如何避免数据分析中的统计陷阱》一第1章 统计显著性简介...
查看>>
《深入理解JavaScript》——1.3 变量和赋值
查看>>
《Python Cookbook(第3版)中文版》——1.14 对不原生支持比较操作的对象排序...
查看>>