转载请注明出处:http://blog.csdn.net/luotuo44/article/details/38501341
之前的博文讲了很多Libevent的基础构件,现在以一个实际例子来初步探究Libevent的基本工作流程。由于还有很多Libevent的细节并没有讲所以,这里的探究还是比较简洁,例子也相当简单。
#include<unistd.h>
#include<stdio.h>
#include<event.h>
#include<thread.h>
void cmd_cb(int fd, short events, void *arg)
{
char buf[1024];
printf("in the cmd_cb\n");
read(fd, buf, sizeof(buf));
}
int main()
{
evthread_use_pthreads();
//使用默认的event_base配置
struct event_base *base = event_base_new();
struct event *cmd_ev = event_new(base, STDIN_FILENO,
EV_READ | EV_PERSIST, cmd_cb, NULL);
event_add(cmd_ev, NULL); //没有超时
event_base_dispatch(base);
return 0;
}
上面代码估计是不会比读者写的第一个Libevent程序复杂。但这已经包含了Libevent的基础工作流程。这里将进入这些函数的内部探究,并且只会讲解之前博文出现过的,没出现的,尽量不讲。在讲解之前,要先了解一下struct event这个结构体。
struct event {
TAILQ_ENTRY(event) ev_active_next; //激活队列
TAILQ_ENTRY(event) ev_next; //注册事件队列
/* for managing timeouts */
union {
TAILQ_ENTRY(event) ev_next_with_common_timeout;
int min_heap_idx; //指明该event结构体在堆的位置
} ev_timeout_pos; //仅用于定时事件处理器(event).EV_TIMEOUT类型
//对于I/O事件,是文件描述符;对于signal事件,是信号值
evutil_socket_t ev_fd;
struct event_base *ev_base; //所属的event_base
//因为信号和I/O是不能同时设置的。所以可以使用共用体以省内存
//在低版本的Libevent,两者是分开的,不在共用体内。
union {
//无论是信号还是IO,都有一个TAILQ_ENTRY的队列。它用于这样的情景:
//用户对同一个fd调用event_new多次,并且都使用了不同的回调函数。
//每次调用event_new都会产生一个event*。这个xxx_next成员就是把这些
//event连接起来的。
/* used for io events */
//用于IO事件
struct {
TAILQ_ENTRY(event) ev_io_next;
struct timeval ev_timeout;
} ev_io;
/* used by signal events */
//用于信号事件
struct {
TAILQ_ENTRY(event) ev_signal_next;
short ev_ncalls; //事件就绪执行时,调用ev_callback的次数 /* Allows deletes in callback */
short *ev_pncalls; //指针,指向次数
} ev_signal;
} _ev;
short ev_events;//记录监听的事件类型 EV_READ EVTIMEOUT之类
short ev_res; /* result passed to event callback *///记录了当前激活事件的类型
//libevent用于标记event信息的字段,表明其当前的状态.
//可能值为前面的EVLIST_XXX
short ev_flags;
//本event的优先级。调用event_priority_set设置
ev_uint8_t ev_pri;
ev_uint8_t ev_closure;
struct timeval ev_timeout;//用于定时器,指定定时器的超时值
/* allows us to adopt for different types of events */
void (*ev_callback)(evutil_socket_t, short, void *arg); //回调函数
void *ev_arg; //回调函数的参数
};
event结构体里面有几个TAILQ_ENTRY队列节点类型。这里因为一个event是会同时处于多个队列之中。比如前几篇博文说到的同一个文件描述符或者信号值对应的多个event会被连在一起,所有的被加入到event_base的event也会连在一起,所有被激活的event也会被连在一起。所以会有多个QAILQ_ENTRY。
event结构体只有一两个之前没有说到的概念,这不妨碍理解event结构体。而event_base结构体则会太多之前没有说到的概念,所以这里就不贴出event_base的代码了。
在读这篇博文前,最好读一下前面几篇博文,因为会用到其他讲到的东西。如果之前有讲过的东西,这里也将一笔带过。
好了,开始探究。
最前面的evthread_use_pthreads();就不多说了,看《多线程、锁、条件变量(一)》和《多线程、锁、条件变量(二)》这两篇博文吧。
创建event_base:
下面看一下event_base_new函数。它是由event_base_new_with_config函数实现的。我们还是看后面那个函数吧。
//event.c文件
struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
int i;
struct event_base *base;
int should_check_environment;
//之所以不用mm_malloc是因为mm_malloc并不会清零该内存区域。
//而这个函数是会清零申请到的内存区域,这相当于被base初始化
if ((base =