学习资料:链接:百度网盘
提取码:jqsp
包括libevent源码剖析,参考手册
另外推荐一个网站:学习网站
libevent是高性能i/o网路库,基于reactor事件驱动模型。
下面这张图直观地展示了libevent的工作模式。
这是Reactor模式的几个必备组件,事件源(Handle),Reactor框架,事件多路分发器(Event Demultiplexer),事件处理器(Event Handle),具体的事件处理器(Concrete Event Handle)。
事件源,一般是句柄,比如文件描述符上面的事件,如I/O事件,信号,定时事件等。
事件多路分发器(Event Demultiplexer),在事件循环中等待并处理事件,I/O复用即是为此服务的,分发器首先register_handle(),将事件源注册到Event Demultiplexer上,当有事件到达时,则发出事件就绪的通知给Event handle,通知其处理。
反应器(Reactor),在libevent中,一个event_base对象即为一个Reactor实例,是一个事件管理的接口,在内部,调用Event Demultiplexer 进行事件的注册,注销,运行事件循环,分发事件等,如图中的三大功能模块:handle_event(),register_event(),remove_event()。
事件处理器( Event Handle),包含了一系列事件处理程序,为外部提供了一组接口,每个接口对应于一个类型的事件,可供Reactor在对应事件发生时调用。此外,get_handle()接口还可以返回发生事件的句柄。通常我们需要将将事件处理器(Event Handle)与句柄绑定,这样可以在事件发生时获取正确的Event Handle。事件处理器对应到libevent中即为event结构体(将“事件处理器”简化为“事件”)
libevent代码分类比较明确,主要包括头文件,内部使用的头文件,辅助功能文件,日志系统,libevent框架,I/O复用的封装,信号管理,定时器管理,缓冲区管理,基本数据结构,基于libevent的两个实用库等。
头文件:libevent2.0之后的版本,头文件目录在include/event2目录下,主要包括接口,事件,主要结构体的声明。同时还包含三类:API头文件,兼容性头文件(摒弃),结构头文件(包含后缀_struct.h的文件)。在结构头文件中实现了声明的各类头文件,如event_struct.h对应event.h的实现,实现了最主要的事件处理器event的结构。
struct event_base;
struct event {
struct event_callback ev_evcallback;
/* for managing timeouts */
union {
TAILQ_ENTRY(event) ev_next_with_common_timeout;
size_t min_heap_idx;
} ev_timeout_pos;
evutil_socket_t ev_fd;
short ev_events;
short ev_res; /* result passed to event callback */
struct event_base *ev_base;
union {
/* used for io events */
struct {
LIST_ENTRY (event) ev_io_next;
struct timeval ev_timeout;
} ev_io;
/* used by signal events */
struct {
LIST_ENTRY (event) ev_signal_next;
short ev_ncalls;
/* Allows deletes in callback */
short *ev_pncalls;
} ev_signal;
} ev_;
struct timeval ev_timeout;
};
内部使用的头文件:*-intenal.h,包含这个后缀的均为内部使用的辅助性头文件,对外不可见,以达到信息隐藏的目的。
辅助功能文件,主要定义在evutil.h和evutil.c文件中,主要实现了一些辅助功能函数,比如创建socket pair,时间操作,如加减比较,将文件描述符设置为非阻塞,重用地址等。除此之外,evutil_rand.c,strlcpy.c,arc4random.c,也提供了一些基本操作,如生成随机数,获取socket地址信息,读取文件,设置socket属性等函数。
日志系统,log.h与log.c两个文件定义了libevent的日志系统,提供了一些日志函数,如event_warn,event_err,event_msgx。在默认情况下,stdout和stderr的目标为终端,这当然不方便守护进程和日后查看的情况,所以libevent还提供了定制了的日志回调函数:event_set_log_callback()
#define EVENT_LOG_DEBUG 0
#define EVENT_LOG_MSG 1
#define EVENT_LOG_WARN 2
#define EVENT_LOG_ERR 3
/* Deprecated; see note at the end of this section */
#define _EVENT_LOG_DEBUG EVENT_LOG_DEBUG
#define _EVENT_LOG_MSG EVENT_LOG_MSG
#define _EVENT_LOG_WARN EVENT_LOG_WARN
#define _EVENT_LOG_ERR EVENT_LOG_ERR
typedef void (*event_log_cb)(int severity, const char *msg);
void event_set_log_callback(event_log_cb cb);
可以通过自定义event_log_cb函数,将指针传递给event_set_log_callback函数,即可完成。
libevent框架,实现于event.c文件中,对应于上文说的几个组件,主要是event与event_base两个结构体的相关操作。
I/O复用的封装。devpoll.c,kqueue.c,evport.c,select.c,win32select.c,poll.c,epoll.c文件,分别封装了对应文件名的I/O复用机制,这些文件的主要内容相同,都是针对结构体eventop所定义的接口函数的具体实现。
信号管理。signal.c文件提供了对信号的支持,其内容也是对eventop所定义的接口函数的具体实现,同时还提供了定制的信号处理接口函数。
定时器管理。定时器管理通常采用堆结构维护,min-heap.h以及minheap-internal.c声明和实现了一个以时间为key的小根堆定时器结构。
缓冲区管理。buffer*.c文件,提供了对网络I/O缓冲的控制,主要包括以下功能:
其次,event_tagging.c文件提供往缓冲区添加标记数据,以及从缓冲区读取标记数据的函数。
compat\sys 下的一个源文件:queue.h 是 libevent 基本数据结构的实现,包括链表,双向链表,队列,尾部队列和循环队列。(在libevent1.x的版本中有两个文件,2.x中只有一个了)
基于libevent的几个实用网络库:http , evdns,rpc:是基于 libevent 实现的 http 服务器,异步 dns 查询库和远程过程调用(rpc协议)的实现;
evthread*.c:提供了锁,多线程,条件变量等机制,即提供了对多线程的支持。
libevent 的结构体在多线程下通常有三种工作方式:
1 某些结构体内在地是单线程的:同时在多个线程中使用它们总是不安全的。
2 某些结构体具有可选的锁:可以告知 libevent 是否需要在多个线程中使用每个对象。
3 某些结构体总是锁定的:如果 libevent 在支持锁的配置下运行,在多个线程中使用它们
总是安全的。
为获取锁,在调用分配需要在多个线程间共享的结构体的 libevent 函数之前,必须告知
libevent 使用哪个锁函数。
综上,在整个源码中,最重要的部分为event-internal.h,include/event2/event_struct.h,event.c,evmap.c等文件最为重要,因为他们定义了event和event_base结构体,实现了结构体的相关操作,也就是整个libevent框架的主要操作,熟读他们对掌握libevent至关重要。(evmap.c用于维护句柄(文件描述符或者信号)与事件处理器的映射关系)。
基本事件流程可以看libevent自带的hello-world.c文件,即是一个简单实例。
/*
This example program provides a trivial server program that listens for TCP
connections on port 9995. When they arrive, it writes a short message to
each client connection, and closes each connection once it is flushed.
Where possible, it exits cleanly in response to a SIGINT (ctrl-c).
*/
#include
#include
#include
#include
#ifndef _WIN32
#include
# ifdef _XOPEN_SOURCE_EXTENDED
# include
# endif
#include
#endif
#include
#include
#include
#include
#include
static const char MESSAGE[] = "Hello, World!\n";
static const int PORT = 9995;
static void listener_cb(struct evconnlistener *, evutil_socket_t,
struct sockaddr *, int socklen, void *);//evutil_socket_t:socket的文件描述符
static void conn_writecb(struct bufferevent *, void *);
static void conn_eventcb(struct bufferevent *, short, void *);
static void signal_cb(evutil_socket_t, short, void *);
int
main(int argc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
struct event *signal_event;
struct sockaddr_in sin = {0};
#ifdef _WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif
base = event_base_new();
if (!base) {
fprintf(stderr, "Could not initialize libevent!\n");
return 1;
}
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
(struct sockaddr*)&sin,
sizeof(sin));
if (!listener) {
fprintf(stderr, "Could not create a listener!\n");
return 1;
}
signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
if (!signal_event || event_add(signal_event, NULL)<0) {
fprintf(stderr, "Could not create/add a signal event!\n");
return 1;
}
event_base_dispatch(base);
evconnlistener_free(listener);
event_free(signal_event);
event_base_free(base);
printf("done\n");
return 0;
}
static void
listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sa, int socklen, void *user_data)
{
struct event_base *base = user_data;
struct bufferevent *bev;
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
fprintf(stderr, "Error constructing bufferevent!");
event_base_loopbreak(base);
return;
}
bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
bufferevent_enable(bev, EV_WRITE);
bufferevent_disable(bev, EV_READ);
bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}
static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
struct evbuffer *output = bufferevent_get_output(bev);
if (evbuffer_get_length(output) == 0) {
printf("flushed answer\n");
bufferevent_free(bev);
}
}
static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
} else if (events & BEV_EVENT_ERROR) {
printf("Got an error on the connection: %s\n",
strerror(errno));/*XXX win32*/
}
/* None of the other events can happen here, since we haven't enabled
* timeouts */
bufferevent_free(bev);
}
static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{
struct event_base *base = user_data;
struct timeval delay = { 2, 0 };
printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");
event_base_loopexit(base, &delay);
}
首先,创建event_base与event实例,以及evconnlistener实例(监听器),event_base对应一个Reactor实例,event对应一个具体时间处理器,此处为信号事件处理器,还有一种为定时信号处理器。event_base_new()也只是简单地初始化参数。
调用evsignal_new()初始化具体的信号事件处理器,evsignal_new为一个宏定义,本质上调用了event_new()函数
#define evsignal_new(b, x, cb, arg) \
event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
event_new()函数也只是做了简单的初始化操作,定义在event.c中。
struct event *
event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg)
其中,base指明了event从属的Reactor,fd指定了与该事件处理器关联的句柄,events用二进制的方式指明事件类型,cb为回调函数,相当于handle_event()方法,arg可以为cb函数提供更多的参数(通常不必要)。
将定义好的事件处理器event添加到注册时间队列中,并将该事件处理器对应的事件添加到事件多路分发器上,这一步相当于register_event()。在这个例子中并没有这样做,如果需要的话,可以通过event_add()函数完成相关操作
int
event_add(struct event *ev, const struct timeval *tv)
event_add中调用了event_add_nolock_()函数(之前版本为event_add_internal()),这个函数完成了大部分的上述操作。
调用event_base_dispatch()函数执行事件主循环。
事件循环结束之后,调用*_free()函数释放资源。
除此之外,代码中还有许多call_back函数,这些函数接口恰好说明了Libevent基于事件回调的工作模式。