libevent的基本操作单元是事件。每个事件代表一组条件的集合,这些条件包括:
(1)文件描述符已经就绪,可以读取或者写入
(2)文件描述符变为就绪状态,可以读取或者写入(仅对于边沿触发IO)
(3)超时事件
(4)发生某信号
(5)用户触发事件
所有事件具有相似的生命周期。调用libevent函数设置事件并且关联到event_base之后,事件进入“已初始化(initialized)”状态。此时可以将事件添加到event_base中,这使之进入“未决(pending)”状态。
在未决状态下,如果触发事件的条件发生(比如说,文件描述符的状态改变,或者超时时间到达),则事件进入“激活(active)”状态,(用户提供的)事件回调函数将被执行。
如果配置为“持久的(persistent)”,事件将保持为未决状态。否则,执行完回调后,事件不再是未决的。删除操作可以让未决事件成为非未决(已初始化)的;添加操作可以让非未决事件再次成为未决的。
2.1 创建事件
使用event_new()接口创建事件。
struct event *event_new(struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb, void *arg); /*这个标志表示某超时时间流逝后事件成为激活的。超时发生时,回调函数的what参数将带有这个标志。*/ /*表示指定的文件描述符已经就绪,可以读取的时候,事件将成为激活的。*/ /*表示指定的文件描述符已经就绪,可以写入的时候,事件将成为激活的。*/ //用于实现信号检测 //表示事件是“持久的” //表示如果底层的event_base后端支持边沿触发事件,则事件应该是边沿触发的。这个标志影响EV_READ和EV_WRITE的语义。 typedef void (*event_callback_fn)(evutil_socket_t fd, short what, void * arg); void event_free(struct event *event);
event_new()试图分配和构造一个用于base的新事件。what参数是上述标志的集合。如果fd非负,则它是将被观察其读写事件的文件。事件被激活时,libevent将调用cb函数,传递这些参数:文件描述符fd,表示所有被触发事件的位字段,以及构造事件时的arg参数。
发生内部错误,或者传入无效参数时,event_new()将返回NULL。
要释放事件,调用event_free()。
使用event_assign二次修改event的相关参数:
int event_assign(struct event *event, struct event_base *base, evutil_socket_t fd, short what, void (*callback)(evutil_socket_t, short, void *), void *arg);
除了event参数必须指向一个未初始化的事件之外,event_assign()的参数与event_new()的参数相同。成功时函数返回0,如果发生内部错误或者使用错误的参数,函数返回-1。
警告:
不要对已经在event_base中未决的事件调用event_assign(),这可能会导致难以诊断的错误。如果已经初始化和成为未决的,调用event_assign()之前需要调用event_del()。
2.2 让事件未决和非未决
2.2.1 让事件未决
所有新创建的事件都处于已初始化和非未决状态,调用event_add()可以使其成为未决的。
int event_add(struct event *ev, const struct timeval *tv);
在非未决的事件上调用event_add()将使其在配置的event_base中成为未决的。成功时函数返回0,失败时返回-1。如果tv为NULL,添加的事件不会超时。否则,tv以秒和微秒指定超时值。
如果对已经未决的事件调用event_add(),事件将保持未决状态,并在指定的超时时间被重新调度。
2.2.2 让事件非未决
int event_del(struct event *ev);
对已经初始化的事件调用event_del()将使其成为非未决和非激活的。如果事件不是未决的或者激活的,调用将没有效果。成功时函数返回0,失败时返回-1。
2.3 设置事件的优先级
多个事件同时触发时,libevent没有定义各个回调的执行次序。可以使用优先级来定义某些事件比其他事件更重要。
int event_priority_set(struct event *event, int priority);
示例代码:
void read_cb(evutil_socket_t, short, void *); void write_cb(evutil_socket_t, short, void *); void main_loop(evutil_socket_t fd) { struct event *important, *unimportant; struct event_base *base; base = event_base_new(); event_base_priority_init(base, 2); important = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL); unimportant = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL); event_priority_set(important, 0); event_priority_set(unimportant, 1); }
2.4 关于事件持久性
默认情况下,每当未决事件成为激活的(因为fd已经准备好读取或者写入,或者因为超时),事件将在其回调被执行前成为非未决的。
如果想让事件再次成为未决的,可以在回调函数中再次对其调用event_add()。然而,如果设置了EV_PERSIST标志,事件就是持久的。这意味着即使其回调被激活,事件还是会保持为未决状态。如果想在回调中让事件成为非未决的,可以对其调用event_del()。
每次执行事件回调的时候,持久事件的超时值会被复位。因此,如果具有EV_READ|EV_PERSIST标志,以及5秒的超时值,则事件将在以下情况下成为激活的:
1. 套接字已经准备好被读取的时候
2. 从最后一次成为激活的开始,已经逝去5秒
2.5 事件循环
一旦有了一个已经注册了某些事件的event_base,就需要让libevent等待事件并且通知事件的发生。
2.5.1 启动事件循环
int event_base_loop(struct event_base *base, int flags); int event_base_dispatch(struct event_base *base);
默认情况下,event_base_loop()函数运行event_base直到其中没有已经注册的事件为止。
执行循环的时候,函数重复地检查是否有任何已经注册的事件被触发(比如说,读事件的文件描述符已经就绪,可以读取了;或者超时事件的超时时间即将到达)。如果有事件被触发,函数标记被触发的事件为“激活的”,并且执行这些事件。
在flags参数中设置一个或者多个标志就可以改变event_base_loop()的行为。如果设置了EVLOOP_ONCE,循环将等待某些事件成为激活的,执行激活的事件直到没有更多的事件可以执行,然后返回。
如果设置了EVLOOP_NONBLOCK,循环不会等待事件被触发:循环将仅仅检测是否有事件已经就绪,可以立即触发,如果有,则执行事件的回调。
完成工作后,如果正常退出,event_base_loop()返回0;如果因为后端中的某些未处理错误而退出,则返回-1。
event_base_dispatch()等同于没有设置标志的event_base_loop()。
示例代码:
void cb_func(evutil_socket_t fd, short what, void *arg) { const char *data = arg; printf("Got an event on socket %d:%s%s [%s]\n", (int) fd, (what&EV_TIMEOUT) ? " timeout" : "", (what&EV_READ) ? " read" : "", data); } void main_loop(evutil_socket_t fd) { struct event *ev; struct timeval five_seconds = {5,0}; struct event_base *base = event_base_new(); ev = event_new(base, fd, EV_TIMEOUT|EV_READ|EV_PERSIST, cb_func, (char*)"Reading event"); event_add(ev, &five_seconds); event_base_dispatch(base); }
2.5.2 停止事件循环
如果想在移除所有已注册的事件之前停止活动的事件循环,可以调用两个稍有不同的函数。
int event_base_loopexit(struct event_base *base, const struct timeval *tv); int event_base_loopbreak(struct event_base *base);
event_base_loopexit()让event_base在给定时间之后停止循环。如果tv参数为NULL,event_base会立即停止循环,没有延时。如果event_base当前正在执行任何激活事件的回调,则回调会继续运行,直到运行完所有激活事件的回调之后才退出。
event_base_loopbreak()让event_base立即退出循环。它与event_base_loopexit(base,NULL)的不同在于,如果event_base当前正在执行激活事件的回调,它将在执行完当前正在处理的事件后立即退出。
注意event_base_loopexit(base,NULL)和event_base_loopbreak(base)在事件循环没有运行时的行为不同:前者安排下一次事件循环在下一轮回调完成后立即停止(就好像带EVLOOP_ONCE标志调用一样);后者却仅仅停止当前正在运行的循环,如果事件循环没有运行,则没有任何效果。
这两个函数都在成功时返回0,失败时返回-1。
示例:立即关闭
void cb(int sock, short what, void *arg) { struct event_base *base = arg; event_base_loopbreak(base); } void main_loop(struct event_base *base, evutil_socket_t watchdog_fd) { struct event *watchdog_event; watchdog_event = event_new(base, watchdog_fd, EV_READ, cb, base); event_add(watchdog_event, NULL); event_base_dispatch(base); }
示例: 执行事件循环10秒,然后退出
void run_base_with_ticks(struct event_base *base) { struct timeval ten_sec; ten_sec.tv_sec = 10; ten_sec.tv_usec = 0; event_base_loopexit(base, &ten_sec); event_base_dispatch(base); puts("Tick"); }
2.5.3 检查循环是否退出
有时候需要知道对event_base_dispatch()或者event_base_loop()的调用是正常退出的,还是因为调用event_base_loopexit()或者event_base_break()而退出的。可以调用下述函数来确定是否调用了loopexit或者break函数。
int event_base_got_exit(struct event_base *base); int event_base_got_break(struct event_base *base);
这两个函数分别会在循环是因为调用event_base_loopexit()或者event_base_break()而退出的时候返回true,否则返回false。下次启动事件循环的时候,这些值会被重设。
2.6 配置一次触发事件
如果不需要多次添加一个事件,或者要在添加后立即删除事件,而事件又不需要是持久的,则可以使用event_base_once()。
int event_base_once(struct event_base *, evutil_socket_t, short, void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);
除了不支持EV_SIGNAL或者EV_PERSIST之外,这个函数的接口与event_new()相同。安排的事件将以默认的优先级加入到event_base并执行。回调被执行后,libevent内部将会释放event结构。成功时函数返回0,失败时返回-1。
2.7 检查事件状态
有时候需要了解事件是否已经添加,检查事件代表什么。
/*event_get_fd()返回为事件配置的文件描述符*/ evutil_socket_t event_get_fd(const struct event *ev); /*event_get_base()返回为事件配置的event_base。*/ struct event_base *event_get_base(const struct event *ev); /*event_get_events()返回事件的标志(EV_READ、EV_WRITE等)。*/ short event_get_events(const struct event *ev); /*event_get_callback()和event_get_callback_arg()返回事件的回调函数及其参数指针。*/ event_callback_fn event_get_callback(const struct event *ev); void *event_get_callback_arg(const struct event *ev); /*获取事件的优先级*/ int event_get_priority(const struct event *ev);
2.8 tcp示例代码
服务器端:tcp_server.c
/************************************************************************* # File Name: tcp_server.c # Author: wenong # mail: huangwenlong@520it.com # Created Time: 2016年09月03日 星期六 21时51分08秒 ************************************************************************/ void read_cb(evutil_socket_t clientfd, short what, void* arg) { int i, recvlen; char buf[MAXBYTES]; struct event* ev = (struct event*)arg; printf("read_cd clientfd %d\n", clientfd); recvlen = read(clientfd, buf, sizeof(buf)); if(recvlen <= 0) { puts("the other size close or error occur"); event_del(ev); event_free(ev); close(clientfd); } for(i = 0; i < recvlen; i++) { buf[i] = toupper(buf[i]); } write(clientfd, buf, recvlen); } void accept_cb(evutil_socket_t serverfd, short what, void * arg) { struct sockaddr_in clientaddr; struct event* ev; int clientaddrlen; int clientfd; puts("Accept client connect"); clientaddrlen = sizeof(clientaddr); bzero((void*)&clientaddr, sizeof(clientaddr)); clientfd = accept(serverfd, (struct sockaddr*)&clientaddr, &clientaddrlen); printf("recv clientfd %d\n", clientfd); ev = event_new((struct event_base*)arg, clientfd, EV_READ | EV_PERSIST, NULL, NULL); event_assign(ev, (struct event_base*)arg, clientfd, EV_READ | EV_PERSIST, (event_callback_fn)read_cb, (void*)ev); event_add(ev, NULL); } void main_loop(evutil_socket_t fd) { struct event_base * base; struct event* ev; base = event_base_new(); ev = event_new(base, fd, EV_READ | EV_PERSIST, (event_callback_fn)accept_cb, (void*)base); event_add(ev, NULL); puts("server begin listenning..."); event_base_dispatch(base); event_free(ev); event_base_free(base); } int main(int argc, char** argv) { int serverfd; socklen_t serveraddrlen; struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(SERVERPORT); serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serverfd = socket(AF_INET, SOCK_STREAM, 0); serveraddrlen = sizeof(serveraddr); bind(serverfd, (struct sockaddr*)&serveraddr, serveraddrlen); listen(serverfd, 128); main_loop(serverfd); close(serverfd); return 0; }
客户端: tcp_client.c
/************************************************************************* # File Name: tcp_client.c # Author: wenong # mail: huangwenlong@520it.com # Created Time: 2016年09月03日 星期六 22时10分11秒 ************************************************************************/ void read_cb(evutil_socket_t clientfd, short what, void* arg) { puts("read clientfd"); char buf[MAXBYTES]; int ret; ret = read(clientfd, buf, sizeof(buf)); if(ret <= 0) { close(clientfd); event_base_loopexit((struct event_base*)arg, NULL); } write(STDOUT_FILENO, buf, ret); } void cmd_msg_cb(evutil_socket_t stdinfd, short what, void* arg) { int ret; int clientfd = (int)arg; char buf[MAXBYTES]; puts("get msg from stdin"); ret = read(stdinfd, buf, sizeof(buf)); write(clientfd, buf, ret); } void main_loop(int clientfd) { struct event_base* base; struct event* ev_socket, *ev_stdin; base = event_base_new(); ev_stdin = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST , (event_callback_fn)cmd_msg_cb, (void*)clientfd); ev_socket = event_new(base, clientfd, EV_READ | EV_PERSIST , (event_callback_fn)read_cb, (void*)base); event_add(ev_socket, NULL); event_add(ev_stdin, NULL); event_base_dispatch(base); puts("event free and exit"); event_free(ev_socket); event_free(ev_stdin); event_base_free(base); } int main(int argc, char** argv) { int clientfd; struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; inet_pton(AF_INET, SERVERIP, &serveraddr.sin_addr.s_addr); serveraddr.sin_port = htons(SERVERPORT); clientfd = socket(AF_INET, SOCK_STREAM, 0); connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); main_loop(clientfd); return 0; }