libevent简单使用

1、安装libevent

Linux上使用如下命令安装

sudo apt-get install libevent-dev

或者是源码安装 http://libevent.org/

#在当前目录下解压安装包:
tar -zxvf libevent-2.0.22-stable.tzr.gz 
cd libevent-2.0.22-stable
#配置安装库的目标路径: 
./configure --prefix=/usr 
#编译安装libevent库:
make
make install
#查看libevent库是否安装成功: 
ls -al /usr/lib | grep libevent

通过函数**event_get_version()**可以查看libevent的版本。

2、使用libevent

步骤和顺序:

①.创建默认的event_base

event_base算是Libevent最基础、最重要的对象,因为修改配置、添加事件等,基本都需要将它作为参数传递进去。

#include <event2/event.h>
struct event_base *event_base_new(void);

event_base_new()函数分配并且返回一个新的具有默认设置的event_base。函数会检测环境变量,返回一个到event_base的指针。如果发生错误,则返回NULL。选择各种方法时,函数会选择OS支持的最快方法。 使用完event_base之后,使用event_base_free()进行释放。

void event_base_free(struct event_base *base);

注意:这个函数不会释放当前与event_base关联的任何事件,或者关闭他们的套接字,或者释放任何指针。 编译的时候需要加上-levent

②.创建事件

使用event_new()接口创建事件。

所有事件具有相似的生命周期。调用libevent函数设置事件并且关联到event_base之后,事件进入“已初始化 (initialized)”状态。此时可以将事件添加到event_base中,这使之进入“未决(pending)”状态

在未决状态下,如果触发事件的条件发生(比如说,文件描述符的状态改变,或者超时时间到达),则事件进 入“激活(active)”状态,(用户提供的)事件回调函数将被执行。

如果配置为“持久的(persistent)”,事件将保持为未决状态。否则,执行完回调后,事件不再是未决的。删除操作可以让未决事件成为非未决(已初始化)的;添加操作可以让非未决事件再次成为未决的。

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()。

③.让事件未决和非未决

让事件未决:

所有新创建的事件都处于已初始化和非未决状态,调用event_add()可以使其成为未决的。

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

在非未决的事件上调用event_add()将使其在配置的event_base中成为未决的。成功时函数返回0,失败时返回-1。如果tv为NULL,添加的事件不会超时。否则,tv以秒和微秒指定超时值。

如果对已经未决的事件调用event_add(),事件将保持未决状态,并在指定的超时时间被重新调度。

让事件非未决:

int event_del(struct event *ev);

对已经初始化的事件调用event_del()将使其成为非未决和非激活的。如果事件不是未决的或者激活的,调用将 没有效果。成功时函数返回0,失败时返回-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()。

如果想在移除所有已注册的事件之前停止活动的事件循环,可以调用两个稍有不同的函数。

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支持哪些特征,或者当前使用哪种方法。 event_get_supported_methods()函数返回一个指针,指向libevent支持的方法名字数组。这个数组的最后一个元素是NULL。

void event_base_free(struct event_base *base);
const char **event_get_supported_methods(void)
const char *event_get_version();

创建复杂的event_base

要对取得什么类型的event_base有更多的控制,就需要使用event_config。event_config是一个容纳event_base 配置信息的不透明结构体。需要event_base时,将event_config传递给event_base_new_with_config。

struct event_config *event_config_new(void);
struct event_base *event_base_new_with_config(const struct event_config *cfg)
void event_config_free(struct event_config *cfg);

禁用特定后端

int event_config_avoid_method(struct event_config *cfg, const char *method)
const char* method;//指定特定的后端,例如"select" "poll" "epoll"

设置特征

调用event_config_require_feature()让libevent不使用不能提供所有指定特征的后端。成功时返回0,失败时返回-1。

int event_config_require_features(struct event_config *cfg,
enum event_method_feature feature)
;
enum event_method_feature {
  EV_FEATURE_ET = 0x01//要求支持边沿触发的后端
  EV_FEATURE_O1 = 0x02//要求添加、删除单个事件,或者确定哪个事件激活的操作是O(1)复杂度的后端 
  EV_FEATURE_FDS = 0x04//要求支持任意文件描述符,而不仅仅是套接字的后端
};

设置标志

调用event_config_set_flag()让libevent在创建event_base时设置一个或者多个将在下面介绍的运行时标志。成功时返回0,失败时返回-1。

int event_config_set_flag(struct event_config *cfg, 
enum event_base_config_flag flag)
;
enum event_base_config_flag {
  EVENT_BASE_FLAG_NOLOCK = 0x01,
  EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
  EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
  EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
  EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
  EVENT_BASE_FLAG_PRECISE_TIMER = 0x20 
};
EVENT_BASE_FLAG_NOLOCK://不要为event_base分配锁。设置这个选项可以为event_base节省一点用于锁定和解锁 的时间,但是让在多个线程中访问event_base成为不安全的。
EVENT_BASE_FLAG_IGNORE_ENV://选择使用的后端时,不要检测EVENT_*环境变量。使用这个标志需要三思:这会让 用户更难调试你的程序与libevent的交互。
EVENT_BASE_FLAG_STARTUP_IOCP://仅用于Windows,让libevent在启动时就启用任何必需的IOCP分发逻辑,而不是按需启用。
EVENT_BASE_FLAG_NO_CACHE_TIME://不缓存时间,在每个超时事件之后重新读取系统时间.
EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST://告诉libevent,如果决定使用epoll后端,可以安全地使用更快的基于changelist的后端。epoll-changelist后端可以在后端的分发函数调用之间,同样的fd多次修改其状态的情况下,避免不必要的系统调用。但是如果传递任何使用dup()或者其变体克隆的fd给libevent,epoll-changelist后端会触发一个内核bug,导致不正确的结果。在不使用epoll后端的情况下,这个标志是没有效果的。也可以通过设置EVENT_EPOLL_USE_CHANGELIST环境变量来打开epoll-changelist选项。

event_base优先级

int event_base_priority_init(struct event_base *base, int n_priorities);

成功时这个函数返回0,失败时返回-1。base是要修改的event_base,n_priorities是要支持的优先级数目,这个 数目至少是1。每个新的事件可用的优先级将从0(最高)到n_priorities-1(最低)。

常量EVENT_MAX_PRIORITIES表示n_priorities的上限。调用这个函数时为n_priorities给出更大的值是错误的。

必须在任何事件激活之前调用这个函数,最好在创建event_base后立刻调用。

libevent实现的TCP服务端代码示例:

#include <iostream>
#include <event2/event.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>

using namespace std;

#define SERVER_PORT 8080

void ReadCB(evutil_socket_t fd, short what, void *arg)
{
    cout << "READCB func start" << endl;
    char buffer[1024] = "welcome to xms";
    int length = send(fd, buffer, strlen(buffer), 0);
    cout << "length = " << length << endl;
    length = recv(fd, buffer, sizeof(buffer) - 10);
    if (length > 0) {
        buffer[length] = '\0';
        cout << buffer << endl;
    }
    cout << "READCB func end" << endl;
    close(fd);
}

void AcceptCB(evutil_socket_t fd, short what, void *arg)
{
    event_base *base = (event_base *)arg;
    sockaddr_in clientAddr;
    socklen_t len = 0;
    int clientFd = accept(fd, (sockaddr*)&clientAddr, &len);
    cout << "clientFd = " << clientFd << endl;
    event *clientEv = event_new(base, clientFd, EV_READ | EV_WRITE | EV_PERSIST | EV_ET, ReadCB, NULL);
    event_add(clientEv, NULL);
}

void MainLoop(int fd)
{
    event_base *base = event_base_new();
    event *listen = event_new(base, fd, EV_READ | EV_WRITE | EV_PERSIST | EV_ET, AcceptCB, base);

    event_add(listen, NULL);
    event_base_dispatch(base);

    event_del(listen);
    event_free(listen);
    event_base_free(base);
}

int main()
{
    struct sockaddr_in saddr;
    memset(&saddr, 0sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(SERVER_PORT);
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (::bind(fd, (sockaddr *)&saddr,sizeof(saddr)) != 0) {
        cout << "bind error " << endl;
    }
    cout << "bind port " << SERVER_PORT << " success" << endl;


    listen(fd, 10);

    MainLoop(fd);
    return 0;

}

3、数据缓冲

很多时候,除了响应事件之外,应用还希望做一定的数据缓冲。比如说,写入数据的时候,通常的运行模式是:

  • (1)决定要向连接写入一些数据,把数据放入到缓冲区中
  • (2)等待连接可以写入
  • (3)写入尽量多的数据
  • (4)记住写入了多少数据,如果还有更多数据要写入,等待连接再次可以写入

这种缓冲IO模式很通用,libevent为此提供了一种通用机制,即bufferevent。bufferevent由一个底层的传输端口(如套接字),一个读取缓冲区和一个写入缓冲区组成。与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调。

每个bufferevent都有一个输入缓冲区和一个输出缓冲区,它们的类型都是“struct evbuffer”。有数据要写入到bufferevent时,添加数据到输出缓冲区;bufferevent中有数据供读取的时候,从输入缓冲区抽取(drain)数据。

每个bufferevent有两个数据相关的回调:一个读取回调和一个写入回调。默认情况下,从底层传输端口读取了任 意量的数据之后会调用读取回调;输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用。通过调 整bufferevent的读取和写入“水位(watermarks)”可以覆盖这些函数的默认行为。 每个bufferevent有四个水位:

  • (1)读取低水位:读取操作使得输入缓冲区的数据量在此级别或者更高时,读取回调将被调用。默认值为0,所以每个读取操作都会导致读取回调被调用。
  • (2)读取高水位:输入缓冲区中的数据量达到此级别后,bufferevent将停止读取,直到输入缓冲区中足够量的数据被抽取,使得数据量低于此级别。默认值是无限,所以永远不会因为输入缓冲区的大小而停止读取。
  • (3)写入低水位:写入操作使得输出缓冲区的数据量达到或者低于此级别时,写入回调将被调用。默认值是0,所以只有输出缓冲区空的时候才会调用写入回调。
  • (4)写入高水位:bufferevent没有直接使用这个水位。它在bufferevent用作另外一个bufferevent的底层传输端口时有特殊意义。

4、bufferevent使用步骤

第一步:创建event_base

struct event_base * event_base_new(void)

第二步:创建 bufferevent 事件

struct bufferevent *  bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options);

参数:base: event_base_new() 创建的对象 fd: 绑定到事件event的文件描述符 options: 一般是0,BEV_OPT_CLOSE_ON_FREE,释放底层的bufferevent。
返回:成功创建的bufferevent 事件对象;

第三步:给bufferevent事件对象设置回调

void  bufferevent_setcb(struct bufferevent *bufev,
                       bufferevent_data_cb readcb, bufferevent_data_cb writecb,
                       bufferevent_event_cb eventcb, void *cbarg)

参数: bufev  :   bufferevent_socket_new()函数的返回值
      readcb :   设置读缓对应的读回调
      回调函数类型:typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx)
;//ctx就是cbarg
我们定义的读缓冲的回调函数内部要调用bufferevent_read(),所以要看下这个函数原型:
ev_size_t bufferevent_read(struct bufferevent *bufev, void *data, ev_size_t size);


     writecb: 设置写缓冲对应的回调
     回调函数类型:typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);//ctx就是cbarg
    bufferevent_write()原型:
    int bufferevent_write(struct bufferevent *bufev, const void *data, ev_size_t size)
    
    
    eventcb:   事件回调,一般用来设置错误回调
        来看一下事件回调的类型:typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, void *ctx)
;

what就是事件类型,类型有:
#define BEV_EVENT_READING    0x01    /**< error encountered while reading */
#define BEV_EVENT_WRITING    0x02    /**< error encountered while writing */
#define BEV_EVENT_EOF        0x10    /**< eof file reached */
#define BEV_EVENT_ERROR    0x20/**< unrecoverable error  encountered */        
#define BEV_EVENT_TIMEOUT    0x40    /**< user-specified timeout reached */
#define BEV_EVENT_CONNECTED 0x80    /**< connect operation finished. */(请求的连接过程已经完成,实现客户端时可用)
cbarg  :  上述回调函数的参数

第四步:缓冲区开启:

注意:新建的bufferevent写缓冲区是enable的,而读缓冲区是disable的,所以读的时候需要使能。

int  bufferevent_enable(struct bufferevent *bufev, short event)
参数:bufev:bufferevent_socket_new()函数的返回值
event:
   #define EV_READ    0x02 //读使能
   #define EV_WRITE    0x04 //写使能

第五步:缓冲区关闭:

int bufferevent_disable(struct bufferevent *bufev, short event);//参数跟bufferevent_enable()一致。

第六步:释放bufferevent

void bufferevent_free(struct bufferevent *bufev);
参数:bufferevent_socket_new的返回值。

基于bufferevent的TCP服务端编程示例:

#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <iostream>
#include <signal.h>
#include <sys/socket.h>

using namespace std;

// telnet 127.0.0.1 20010

void ReadCB(struct bufferevent *bev, void *ctx)
{
    char buf[1024] = {0};
    int len = bufferevent_read(bev, buf, sizeof(buf) - 1);
    cout << "read len = " << len << endl;
    cout << buf << endl;

    // 插入buffer链表,监听到sock可写,才去写
    bufferevent_write(bev, "OK"3);
}

void WriteCB(struct bufferevent *bev, void *ctx)
{
    cout << "write CB" << endl;
}

void EventCB(struct bufferevent *bev, short what, void *ctx)
{
    cout << "eventCB" << endl;
    if (what & BEV_EVENT_TIMEOUT && what & BEV_EVENT_READING)
    {
        cout << "Read Timeout" << endl;
        //读取缓冲区的内容

        //然后关掉
        bufferevent_free(bev);
    }
    else if (what & BEV_EVENT_TIMEOUT && what & BEV_EVENT_WRITING)
    {
        cout << "Write Timeout" << endl;
        //缓冲回滚

        //然后关掉
        bufferevent_free(bev);
    }
    else if (what & BEV_EVENT_ERROR)
    {
        cout << "BEV_EVENT_ERROR" << endl;

        //关掉
        bufferevent_free(bev);
    }
    // 关闭连接的正常错误
    else if (what & BEV_EVENT_EOF)
    {
        cout << "BEV_EVENT_EOF" << endl;
        // 考虑缓冲的处理

        //关掉
        bufferevent_free(bev);
    }
}

void ListenCB(struct evconnlistener *evc, evutil_socket_t client_socket,
              struct sockaddr *client_addr,
              int socklen, void *arg)

{
    cout << "ListenCB" << endl;
    char ip[16] = {0};
    sockaddr_in *addr = (sockaddr_in *)client_addr;
    evutil_inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip));
    cout << "client ip is = " << ip << endl;

    // 创建bufferevent上下文  内部创建了event对象read 和 write
    // BEV_OPT_CLOSE_ON_FREE 关闭bev时关闭socket
    event_base *base = (event_base *)arg;
    bufferevent *bev = bufferevent_socket_new(base, client_socket, BEV_OPT_CLOSE_ON_FREE);
    if (bev == NULL)
    {
        cerr << "bufferevent new failed" << endl;
    }

    // 添加监控事件
    bufferevent_enable(bev, EV_READ | EV_WRITE);

    // 超时时间设定,秒,微秒  读超时 和 写超时
    timeval t1 = {100};
    bufferevent_set_timeouts(bev, &t1, 0);

    // 设置回调函数
    bufferevent_setcb(bev, ReadCB, WriteCB, EventCB, base);
}

int main()
{
    // 创建libevent 上下文,默认是创建base锁
    event_base *base = event_base_new();

    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(20010);
    sin.sin_addr.s_addr = htonl(INADDR_ANY);

    auto evc = evconnlistener_new_bind(base, ListenCB, base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,
                                       10, (sockaddr *)&sinsizeof(sin));

    // 事件主循环,监控事件是否发送,分发事件到回调函数
    // 如果没有事件注册,则退出
    event_base_dispatch(base);

    evconnlistener_free(evc);
    event_base_free(base);

    return 0;
}

五、连接监听器,接收TCP连接

客户端:一般的创建socket客户端步骤:socket(); connect();

int bufferevent_socket_connect(struct bufferevent *bev,struct hal_sockaddr *sa, int socklen);

参数:bev:即bufferevent_socket_new()函数的返回值。注意:fd已经封装在 bev对应的类型即bufferevent 里面了。

sa: 即服务器的IP地址结构;

socklen: sa的长度;

服务器: 一般的创建socket服务器步骤:socket();bind();listen();accept();

bufferevent中的evconnlistener_new_bind()函数可替代 socket();bind();listen();accept();这四个函数。
evconnlistener_new_bind()函数原型:
struct evconnlistener *evconnlistener_new_bind(struct event_base *base,
evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
const struct hal_sockaddr *sa, int socklen)
;

参数:

base:event_base_new()创建的base对象;

cb : listen 回调函数

回调函数的类型:typedef void (*evconnlistener_cb)(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr);

回调函数调用的时间:当接收到新连接时(即当有客户端连接时)会调用回调函数;

回调参数:

listener:成功创建的监听器,即evconnlistener_new_bind返回值;

sock: 新接收的套接字,即客户端的套接字;

addr和len;分别是客户端的地址结构和长度;

ptr:外部ptr传进来的参数。

ptr : 回调函数的参数

flags:

LEV_OPT_CLOSE_ON_FREE:如果设置了这个选项,释放连接监听器会关闭底层套接字;

LEV_OPT_REUSEABLE: 端口复用

backlog:int listen(SOCKET sockfd, int backlog);函数的第二个参数,-1,默认最大值;

sa:服务器的IP+port;

socklen: sa的长度

返回值:struct evconnlistener * ,成功创建的监听器。

释放创建成功的监听器对象:void evnlistener_free(struct evconnlistener *lev);

posted @ 2022-06-06 21:23  mengchao  阅读(1024)  评论(0编辑  收藏  举报