您的位置:首页 > 其它

Libevent应用 (二) 与事件一起工作

2018-03-03 16:54 302 查看

2 与事件一起工作

​ 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参数将带有这个标志。*/
#define EV_TIMEOUT     0x01

/*表示指定的文件描述符已经就绪,可以读取的时候,事件将成为激活的。*/
#define EV_READ         0x02

/*表示指定的文件描述符已经就绪,可以写入的时候,事件将成为激活的。*/
#define EV_WRITE       0x04

#define EV_SIGNAL       0x08 //用于实现信号检测
#define EV_PERSIST     0x10 //表示事件是“持久的”
#define EV_ET           0x20 //表示如果底层的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);示例代码:#include <event2/event.h>

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 启动事件循环

#define EVLOOP_ONCE             0x01
#define EVLOOP_NONBLOCK         0x02
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()。示例代码:#include <event2/event.h>

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。示例:立即关闭#include <event2/event.h>

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秒,然后退出#include <event2/event.h>

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秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <event2/event.h>
#define SERVERPORT 8888
#define MAXBYTES 1024

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秒
************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <event2/event.h>
#define SERVERIP "127.0.0.1"
#define SERVERPORT 8888
#define MAXBYTES 1024
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: