libevent和libev都是c语言实现的异步事件库,主要封装了三个事件,让我们在开发时不需要关注网络IO事件对应的细节,以及定时事件中应使用的数据结构,以及何时调用定时任务,所以只需要关注简单的注册和回调流程。这三个事件是网络io事件,定时事件,信号事件。
libev在libevent基础上做了一些优化。
libevent由于过度依赖全局变量,没有携带上下文信息,对多线程不太友好,需要考虑加锁的问题。libev在注册事件时携带了callback函数,把所需的上下文(具体的事件、事件所依赖的管理器如epoll)给携带过来,方便做多线程。
libevent和libev都是使用最小堆来管理定时任务,区别在于libevent使用的是二叉树,而libev使用四叉树。这个区别在定时事件>50000时,libev会有5% 的性能提升。此外,libev定时器支持具体时间的设置。
libevent实现了很多http组件(如dns、openssl等),实现了很多具体的协议,杂糅在具体网络IO的封装中,而libev只关注网络事件的封装,包括bufferevnt和evconnlistener都没有。但是,作为优化,libev还是封装了libevent同样的接口。
libev对于windows的支持不太友好,主要体现在iocp上,因此有了libuv,在libev上封装了一层iocp。
封装了读缓冲区和写缓冲区,因此可以不用关注具体读写事件的差异。进一步管理读写事件,会提供三个回调:读回调、写回调、事件回调。
封装了accpet()。
这里介绍一下accept()出错返回-1,errno=ENFILE的情况。此时fd数量达到上限,无法再创建fd。有几种解决方案:
1.重新设置fd上限。之前介绍过,不再赘述。
2.预先创建一个空fd。在一开始通过socket()创建一个空fd,不与任何连接绑定,当fd达到上限时,先关闭这个fd,再创建一个新的fd作为clientfd,告诉对端连接已满,再关闭这个连接,再建个空fd。
3.自己限制连接数。自己设置一个限制,要求连接数不超过某个值,这个值应小于系统的限制。
0.注意不能使用边沿触发。如果accpet()返回-1,就不会从全连接队列中取出连接。虽然epoll确实不会因为连接到达而继续触发,但是如果关闭了一些fd,可以建立新连接了,而此时epoll却因为边沿触发不会再被触发,即使有新连接到达。
注意与epoll接口对应,方便理解。
struct event_base *event_base_new(void);
初始化 libevent。对应 epoll_create()。
创建事件,初始化event和相应的回调函数。
struct event * event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg);
设置事件。
void event_set(struct event *ev, evutil_socket_t fd, short events,void (*callback)(evutil_socket_t, short, void *), void *arg);
建立 event 与 event_base 的映射关系。
int event_base_set(struct event_base *eb, struct event *ev);
注册事件,包括时间事件。
int event_add(struct event *ev, const struct timeval *tv);
注意添加事件之前,需要先event_base_set()绑定,将事件绑定在管理器上。
注销事件。
int event_del(struct event *ev);
以上四个函数对应epoll_ctl()。
进入事件循环。相当于epoll_wait()。
int event_base_loop(struct event_base *base, int flags);
libev有多个队列:
注册队列:将IO事件放在里面。检测到事件后就放到就绪队列中。
就绪队列:包括IO事件、定时器事件。会在event_base_loop中()依次调用,会调用这些事件的回调函数,这些回调函数不能是阻塞函数,否则会影响其他IO的处理。
堆队列:把定时器放到这个最小堆中。
libev只保留了最基础的数据结构:网络IO、定时器事件、信号事件,目标是成为unix系统中的工具集。
#define ev_io_init(ev,cb,fd,events) do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)
初始化 watcher 的 fd/events/callback。
void ev_io_start(struct ev_loop *loop, ev_io *w);
把事件绑定到ev_loop(事件管理器,相当于event_base)中。
void ev_timer_start(struct ev_loop *loop, ev_timer *w);
把定时器事件绑定到事件管理器中。
int ev_run(struct ev_loop *loop, int flags);
开启事件循环,相当于epoll_wait()中的事件循环。
最后再提示一下,libev还封装了与libevent一样的接口,只不过里面是由libev实现的。