1. 首页 > 智能数码 >

libevent详解(libevent参考手册)

libevent : 名气最大,应用最广泛,历史悠久的跨平台事件库;

libevent详解(libevent参考手册)libevent详解(libevent参考手册)


libev : 较libevent而言,设计更简练,性能更好,但对Windows支持不够好;

libuv : 开发node的过程中需要一个跨平台的事件库,他们首选了libev,但又要支持Windows,故重新封装了一套,*nix下用libev实现,Windows下用IOCP实现;

Libevent的最基本操作单元就是event,每个event表示一系列状况:

events都有相同的生命周期。一旦我们调用Libevent的函数去创建一个event而且绑定在一个event_base上,这就算是初始化了。这时我们可以添加在这个event上关注的要发生的事件,然后当事件发生后,此event就被激活,然后回调函数就会被执行。如果这个event设置为一直保持的状态,就会一直不停地关注事件。不然,只会关注一次。

event_new函数分配创建一个新的event绑定在base上,fd是我们关注读写事件的文件描述符,当event激活后,Libevent会调用提供的cb回调函数。出错的情况,会返回NULL;

销毁一个event,调用event_free()函数。

但要真正让Libevent开始关注事件,还需要event_add()函数

例子

对于这种情况,我们不能直接传一个指向event的指针给event_new,因为它此时还不存在。为了解决这种情况,我们可以用函数event_self_cbarg()

Libevent提供了一系列方便的宏

Libevent可以监听POSIX信号

也有一些对于信号事件方便的宏:

使用event_assign会造成难以诊断的错误,这时因为不同版本的Libevent里event结构的大小不同的原因。除非有了从堆上分配event有明显的性能不好的表现,我们应该尽量选择event_new()

我们也可以用event_assign去初始化栈分配和static栈初始化的events

永远不要在event已经绑定event_base后还在这个event上调用event_assign(),这会导致难以诊断的错误

当有多个event在同时激活,Libevent不会定义任何执行的顺序。我们可以通过定义优先级来达到

在调用event_add之前,调用函数

设置优先级。优先级范围是0和这个event_base里有的优先级数量。

当多个事件同时激活,Libevent先跑优先级高的,然后再继续循环去检查,当没有高优先级的时候才会去执行低优先级的。

Libevent提供了一系列函数

例子

但注意event_active会造成无限循环的使用,例如错误示范:

正确示范:

目前的Libevent使用二叉堆来存储时间事件,一般情况下的表现是O(lg n),但是当有很多相同时间事件时就会不同了。

例如,假如我们有一万个相同时间的时间事件,如果用双向链表队列只会用O(1)。

Libevent允许我们放这样的时间事件在链表队列里,其他的在二叉堆里,这样的话可以向Libevent寻求一个“相同时间”时间事件

libevent是一个轻量级的基于事件驱动的高性能的开源网络库,并且支持多个平台,对多个平台的I/O复用技术进行了封装,当我们编译库的代码时,编译的脚本将会根据OS支持的处理事件机制,来编译相应的代码,从而在libevent接口上保持一致。

在当前的服务器上,面对的主要问题就是要能处理大量的连接。而通过libevent这个网络库,我们就可以调用它的API来很好的解决上面的问题。首先,可以来回顾一下,对这个问题的传统解决方法。

问题: 如何处理多个客户端连接

解决方案1: I/O复用技术

这几种方式都是同步I/O,即当读写事件就绪,他们自己需要负责进行读写,这个读写过程是阻塞的,而异步I/O则不需要自己负责读写,只需要通知负责读写的程序就可以了。

解决方案2: 多线程技术或多进程技术

多线程技术和多进程技术也可以处理高并发的数据连接,因为在服务器中可以产生大量的进程和线程和处理我们需要监视的连接。但是,这两种方式也是有很大的局限性的,比如多进程模型就不适合大量的短连接,因为进程的产生和关闭需要消耗较大的系统性能,同样,还要进程进程间的通信,在CPU性能不足的情况下不太适合。而多线程技术则不太适合处理长连接,因为当我们建立一个进程时,linux中会消耗8G的栈空间,如果我们的每个连接都杵着不断开,那么大量连接长连接后,导致的结果就是内存的大量消耗。

解决方案3: 常用的上述二者复合使用

上述的两种方法各具有优缺点,因此,我们可以将上述的方法结合起来,这也是目前使用较多的处理高并发的方法。多进程+I/O复用或者多线程+I/O复用。而在具体的实现上,又可以分为很多的方式。比如多线程+I/O复用技术,我们使用使用一个主线程负责监听一个端口和接受的描述符是否有读写事件产生,如果有,则将事件分发给其他的工作进程去完成,这也是进程池的理念。

在说完上述的高并发的处理方法之后,我们可以来介绍一个libevent的主要特色了。

同样,lievent也是采用的上述系统提供的select,poll和epoll方法来进行I/O复用,但是针对于多个系统平台上的不同的I/O复用实现方式,libevent进行了重新的封装,并提供了统一的API接口。libevent在实现上使用了事件驱动这种机制,其本质上是一种Reactor模式。

在Libevent中也是一样,向Libevent框架注册相应的事件和回调函数;当这些事件发生时,Libevent会调用这些回调函数处理相应的事件。

lbevent的事件支持三种,分别是网络IO、定时器和信号。定时器的数据结构使用最小堆(Min Heap),以提高效率。网络IO和信号的数据结构采用了双向链表(TAILQ)。

更多linux内核视频教程文本资料免费获取后台私信【内核】。

libevent的安装很简单,我是直接从github上clone下一个源码,然后进行编译安装的。

具体的命令是(假设你已经安装了git):

现在的libevent版本已经到达libevent2了,其增加了多线程的支持,API函数也发生了一些微小的变化。

如果你想知道更多的API使用情况,请点击这里。

下面,就基于libevent2编写一个聊天室服务器。

设计思想: 首先创建一个套接字,进而创建一个事件对此端口进行监听,将所请求的用户组成一个队列,并监听所有的用户事件,当某个用户说话了,产生了读事件,就将该用户的发言发送给队列中的其他用户。

程序分析

需要包含的libevent函数头:

创建一个client结构体,接受连接后存放数据:

先来看下mian函数的处理:

首先,函数初始化了一个用户队列tailq,接着创建了一个socket套接字,并将套接字设定为非阻塞模式,接着对一个全局的evbase事件集合,注册了事件,事件源是listen_fd,回调函数是on_accept,事件发生的情况是EV_READ,而且标志EV_PESIST表明该事件一直存在,而后开启事件扫描循环event_base_dispatch(evbase)。

再看一下回调函数on_accpet实现:

这个回调函数的作用很显然,就是接受了一个客户端的请求,并申请好了一个client信息,将需要的内容填写好,在填写中需要注意的是,又向上述的事件集evbase中注册了一个bufferevent事件client->buf_ev,并注册了回调函数buffered_on_read,buffered_on_error,这三个函数分别是当接受后的连接发生了读或者错误事件后的执行函数。接着,将用户的client结构放入了用户的队列tailq中去。

用户的buffer可读后的执行函数:

执行函数的作用很明显,将libevent管理中的buffer数据读取出,存入本地的data数组内,然后对队列中的client进行检索,如果不是发数据的client,则将数据写入该client的buffer中,发送给该用户。这里注意的是需要反复读取buffer中的数据,防止一个读取并没有读取干净,直到读取不到数据为止。

buffer出错处理函数和上述函数差不多,功能就是出错后,结束掉保存的client结构,详细就不说了。

编译的时候记得修改Makefile中Libevent文件夹的位置

设计思想: 所谓回显服务器就是将客户端发过来的数据再发回去,这里主要也就是说明libevent的纯IO复用实现。实现方法和上面的差不多,甚至可以说更加简单。

程序和上面的聊天服务器差不多,只是在buffer可读的事件函数中,不是将用户的数据发送给其他用户,而是直接发送给用户本身。

设计思想: 上面的方法单纯使用libevent的简单函数来实现服务,但是这里,我们假设我们需要处理的客户端很少,于是我们可以使用对于每个连接我们分配一个线程这样的方式来实现对用户的服务。这种方式简单有效,一对一服务,就算业务逻辑出现阻塞也不怕。

程序分析

首先定义了一些数据结构,worker数据结构定义的是一个工作者,它包含有一个工作线程,和结束标志,需要获取的工作队列,和建立链表需要的指针。job数据结构定义的是操作一个job的方法和对象,这回到程序中,实际上就是指的是事件发生后,封装好的client结构体和处理这个结构体的方法。workqueue数据结构指的是当前的工作队列中的工作者,以及工作队列中的待完成的工作,以及互斥锁和条件变量(因为多个工作进程需要访问这些资源)。

具体的流程就是,用一个主线程监听一个套接字,并将套接字接受到的连接accept,并创建一个client数据结构保存该连接的信息,在这个client结构中注册一个bufferevent事件,注册到client->evbase上(这时候这是向client中的evbase注册了一个事件还没有进行循环这个事件集)。

接着,当监听到某个client有bufferevent事件发生,主线程就把该client结构体和需要进行的工作方法包装成一个job结构,然后把这个job扔到workqueue上去,并通知各个工作者。而后,各个工作者开着的线程就被激活了,疯狂地去workqueue上去抢工作做,某个worker拿到工作后,就可以解包job,根据job的工作说明书(job_function)操作工作对象(client)了。这里,job的工作说明有是循环client中的client->evbase,于是这样线程就会一直去监视这个连接的状态,如果有数据就这会调用回调函数进行处理。同时,这个线程也就是阻塞在这里,这对这一个连接负责。

建立workqueue需要的结构体和函数有:

主线程的on_accept函数为:

job中的工作指南为:

设计思想: 假设我们的用户很多,高并发,长连接,那么我们还是来用I/O复用和线程池实现吧,用一个控制线程通过I/O复用负责监听和分发事件,用一组线程池来进行处理事件,这样就可以灵活地将控制逻辑和业务逻辑分开了,见下述讲解。

程序分析

具体的流程和上面的差不多,用一个主线程监听一个套接字,并将套接字接受到的连接accept,并创建一个client数据结构保存该连接的信息,在这个client结构中注册一个bufferevent事件,但是这里,将事件注册到accept_evbase中,仍然用主线程进行监听。

而面对监听后出现的事件,将client和操作client的方法打包成一个job,放到上述的workqueue中去,让工作进程来完成。这样的操作和上述的差别在于上述方法将bufferevent注册到client中的evbase中,用工作线程监听,而本方法用主线程监听,工作线程负责处理监听产生的事件。

这要的差别在于两个函数 on_accept函数:

在buffered_on_read中,提交job。

在job工作指南server_job_function中就可以做你工作该做的事儿了,根据发来的信息进行数据库处理,http返回等等。

麻烦说详细一点

44. 默认情况下管理员创建了一个用户,就会在________目录下创建一个用户主目录。

A. /usr B. /home C. /root D. /var

47.linux交换分区的作用是________

A. 保存系统软件 B. 保存访问过的网页文件

C.虚拟内存空间 D. 作为用户的主目录

57.在Linux操作系统中手工安装Apache服务器时,默认的Web站点的目录为____。

A. /etc/httpdB. /var/log/httpdC. /etc/homeD. /home/httpd

58.在Linux中,__ __命令可用显示当前用户的工作目录。

A. #whereB. #md C. #pwdD. #rd

59. 在Linux 操作系统中把外部设备当作文件统一管理,外部设备文件通常放在___ ___目录中。

A./devB./libC./etcD./bin

63. 网络操作系统主要解决的问题是________。

A. 网络用户使用界面 B. 网络资源共享与网络资源安全访问限制

C. 网络资源共享 D. 网络安全防范

70.用命令ls -al显示出文件ff的描述如下所示,由此可知文件ff的类型为 。

-rwxr-xr– 1 root root 599 Cec 10 17:12 ff

A 普通文件 B 硬链接 C 目录 D 符号链接

11.若要使用进程名来结束进程,应使用( )命令。

A.kill B.ps C.pss D.pstree

4.修改以太网mac地址的命令为( )。

A.ping B.ifconfig C.arp D.traceroute

9.删除文件命令为( )

A.mkdir B.move C.mv D.rm

19.建立一个新文件可以使用的命令为( )。

A.chmod B.more C.cp D.touch

1. 关于Linux内核版本的说法,以下错误的是( )。

A.表示为主版本号.次版本号.修正号 B.1.2.3表示稳定的发行版

C.1.3.3表示稳定的发行版 D.2.2.5表示对内核2.2的第5次修正

2. 自由软件的含义是( )。

A.用户不需要付费 B.软件可以自由修改和发布

C.只有软件作者才能向用户收费 D.软件发行商不能向用户收费

5. 下列设备属于块设备的是( )。

A.键盘 B.终端

C.游戏杆 D.硬盘

8. 添加用户时使用参数( )可以指定用户目录。

A. -d B. -p

C. -u D. -c

9. 修改用户自身的密码可使用( )

A. passwd B. passwd -d mytest

C. passwd mytest D. passwd -l

13. 若使pid进程无条件终止使用的命令是( )。

A. kill -9 B. kill -15

C. killall -1 D. kill -3

14. 显示系统主机名的命令是( )

A. uname -r B. who am i

C. uname -n D. whoami

17. 202.196.100.1是何类地址( )

A、A类 B、B类

C、C类 D、D类

是先列出 /usr/lib 下的所有文件名,然后 用 grep 过滤出含有 libevent 的行。

列出 /usr/lib目录下 包含libevent这个字符串的 所有 项

可以分两条命令来理解

ls -al /usr/lib

列出该目录下的所有项

grep libevent

grep 包括libevent这个字符串的

请采纳。

epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现。

网上现在关于这两者不同的介绍已经到处都是了。我这里也不能多说出什么东西,只是记录下我看了实现代码之后的一些总结。

两者的使用场景一般是通过一个入口能够同时监控多路I/O。一般使用的接口,

epool就是

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

select为:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

通过上述两个函数能够将调用线程阻塞,线程变为可执行条件有两种情况:

无任何事件发生,超时时间已过

在所控制的I/O有事件到来

epoll_wait函数中看不到相关的监控信息,因为是通过epoll_ctl已经加入,而select之间在函数调用中由(fd_set *readfds, fd_set *writefds, fd_set *exceptfds)传入。epoll_wait饭互结果通过events返回,而select的传入参数也是传出参数。两者传出参数均表示发生事件的对应I/O标识。

两种方式的区别主要体现在以下几个方面:

select所能控制的I/O数有限,这主要是因为fd_set数据结构是一个有大小的,相当与一个定长所数组。

select每次都需要重新设置所要监控的fd_set(因为调用之后会改变其内容),这增加了程序开销。

select的性能要比epoll差,具体原因会在后续内容中详细说明。

嗯,说道这个为什么select要差,那就要从这个select API说起了。这个传进去一个数组,内部实现也不知道那个有哪个没有,所以要遍历一遍。假设说我只监控一个文件描述符,但是他是1000。那么select需要遍历前999个之后再来poll这个1000的文件描述符,而epoll则不需要,因为在之前epoll_ctl的调用过程中,已经维护了一个队列,所以直接等待事件到来就可以了。

Linux中select此段相关代码为:

/* 遍历所有传入的fd_set */

for (i = 0; i < n; ++rinp, ++routp, ++rexp) {

unsigned long in, out, ex, all_bits, bit = 1, mask, j;

unsigned long res_in = 0, res_out = 0, res_ex = 0;

const struct file_operations *f_op = NULL;

struct file *file = NULL;

in = *inp++; out = *outp++; ex = *exp++;

all_bits = in | out | ex;

/* 此处跳无需监控的fd, 白白的浪费时间啊…… */

if (all_bits == 0) {

i += __NFDBITS;

continue;

}/* 后续进行一些相关操作 */

}而epoll则无需进行此类操作,直接检测内部维护的一个就绪队列,如果队列有内容,说明有I/O就绪,那么直接赋值返回内容,成功返回,如果没有成功,那么睡眠,等待就绪队列非空。

通过这个两者的比较,其实两者的差距啊,大部分是因为这个API设计所决定的,select就设计成这样一个API,内部再怎么优化也只能是这么个烂样子,而epoll这样维护与等待分离,灵活多变,最后也就带来了相对的高性能,以及可扩展性。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至836084111@qq.com 举报,一经查实,本站将立刻删除。

联系我们

工作日:9:30-18:30,节假日休息