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

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值