libevent学习笔记

libevent学习笔记

学习资料:链接:百度网盘
提取码:jqsp
包括libevent源码剖析,参考手册

另外推荐一个网站:学习网站

libevent工作结构

libevent是高性能i/o网路库,基于reactor事件驱动模型。

下面这张图直观地展示了libevent的工作模式。

libevent学习笔记_第1张图片

这是Reactor模式的几个必备组件,事件源(Handle),Reactor框架,事件多路分发器(Event Demultiplexer),事件处理器(Event Handle),具体的事件处理器(Concrete Event Handle)。

  1. 事件源,一般是句柄,比如文件描述符上面的事件,如I/O事件,信号,定时事件等。

  2. 事件多路分发器(Event Demultiplexer),在事件循环中等待并处理事件,I/O复用即是为此服务的,分发器首先register_handle(),将事件源注册到Event Demultiplexer上,当有事件到达时,则发出事件就绪的通知给Event handle,通知其处理。

  3. 反应器(Reactor),在libevent中,一个event_base对象即为一个Reactor实例,是一个事件管理的接口,在内部,调用Event Demultiplexer 进行事件的注册,注销,运行事件循环,分发事件等,如图中的三大功能模块:handle_event(),register_event(),remove_event()。

  4. 事件处理器( Event Handle),包含了一系列事件处理程序,为外部提供了一组接口,每个接口对应于一个类型的事件,可供Reactor在对应事件发生时调用。此外,get_handle()接口还可以返回发生事件的句柄。通常我们需要将将事件处理器(Event Handle)与句柄绑定,这样可以在事件发生时获取正确的Event Handle。事件处理器对应到libevent中即为event结构体(将“事件处理器”简化为“事件”)

libevent源文件结构

libevent代码分类比较明确,主要包括头文件,内部使用的头文件,辅助功能文件,日志系统,libevent框架,I/O复用的封装,信号管理,定时器管理,缓冲区管理,基本数据结构,基于libevent的两个实用库等。

  1. 头文件: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;
    };
    
  2. 内部使用的头文件:*-intenal.h,包含这个后缀的均为内部使用的辅助性头文件,对外不可见,以达到信息隐藏的目的。

  3. 辅助功能文件,主要定义在evutil.h和evutil.c文件中,主要实现了一些辅助功能函数,比如创建socket pair,时间操作,如加减比较,将文件描述符设置为非阻塞,重用地址等。除此之外,evutil_rand.c,strlcpy.c,arc4random.c,也提供了一些基本操作,如生成随机数,获取socket地址信息,读取文件,设置socket属性等函数。

  4. 日志系统,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函数,即可完成。

  5. libevent框架,实现于event.c文件中,对应于上文说的几个组件,主要是event与event_base两个结构体的相关操作。

  6. I/O复用的封装。devpoll.c,kqueue.c,evport.c,select.c,win32select.c,poll.c,epoll.c文件,分别封装了对应文件名的I/O复用机制,这些文件的主要内容相同,都是针对结构体eventop所定义的接口函数的具体实现。

  7. 信号管理。signal.c文件提供了对信号的支持,其内容也是对eventop所定义的接口函数的具体实现,同时还提供了定制的信号处理接口函数。

  8. 定时器管理。定时器管理通常采用堆结构维护,min-heap.h以及minheap-internal.c声明和实现了一个以时间为key的小根堆定时器结构。

  9. 缓冲区管理。buffer*.c文件,提供了对网络I/O缓冲的控制,主要包括以下功能:

    1. 输入输出数据过滤
    2. 传输速率限制
    3. 使用SSL协议对应用数据进行加密
    4. 零拷贝的文件传输操作

    其次,event_tagging.c文件提供往缓冲区添加标记数据,以及从缓冲区读取标记数据的函数。

  10. compat\sys 下的一个源文件:queue.h 是 libevent 基本数据结构的实现,包括链表,双向链表,队列,尾部队列和循环队列。(在libevent1.x的版本中有两个文件,2.x中只有一个了)

  11. 基于libevent的几个实用网络库:http , evdns,rpc:是基于 libevent 实现的 http 服务器,异步 dns 查询库和远程过程调用(rpc协议)的实现;

  12. 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基本事件处理流程

基本事件流程可以看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);
}

  1. 首先,创建event_base与event实例,以及evconnlistener实例(监听器),event_base对应一个Reactor实例,event对应一个具体时间处理器,此处为信号事件处理器,还有一种为定时信号处理器。event_base_new()也只是简单地初始化参数。

  2. 调用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函数提供更多的参数(通常不必要)。

  3. 将定义好的事件处理器event添加到注册时间队列中,并将该事件处理器对应的事件添加到事件多路分发器上,这一步相当于register_event()。在这个例子中并没有这样做,如果需要的话,可以通过event_add()函数完成相关操作

    int
    event_add(struct event *ev, const struct timeval *tv)
    

    event_add中调用了event_add_nolock_()函数(之前版本为event_add_internal()),这个函数完成了大部分的上述操作。

  4. 调用event_base_dispatch()函数执行事件主循环。

  5. 事件循环结束之后,调用*_free()函数释放资源。

除此之外,代码中还有许多call_back函数,这些函数接口恰好说明了Libevent基于事件回调的工作模式。

你可能感兴趣的:(工作)