【Libevent】Libevent数据行为

1.数据缓存bufferevent

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

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

libevent为此IO机制提供了一种通用机制,即bufferevent。

bufferevent由一个底层的传输端口(如套接字 ),一个读取缓冲区和一个写入缓冲区组成。

与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent 在读取或者写入了足够量的数据之后调用用户提供的回调。

有多种共享公用接口的bufferevent类型,编写本文时已存在以下类型:

  • 基于套接字的 bufferevent:使用 event_*接口作为后端,通过底层流式套接字发送或者接收数据的 bufferevent
  • 异步 IO bufferevent:使用 Windows IOCP 接口,通过底层流式套接字发送或者接收数据的 buffereven
  • 过滤型 bufferevent:将数据传输到底层 bufferevent 对象之前,处理输入或者输出数据的 bufferevent:比如说,为了压缩或者转换数据。
  • 成对的 bufferevent:相互传输数据的两个 bufferevent。

关于evbuffer

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

evbuffer支持部分接口可以对缓冲区进行操作。下面的内容会对其接口进行介绍

1.1回调和水位

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

每个bufferevent有下面四个水位

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

创建 bufferevent 时可以使用一个或者多个标志修改其行为。可识别的标志有:

  • BEV_OPT_CLOSE_ON_FREE :释放bufferevent时关闭底层传输端口。这将关闭底层套接字,释放底层 bufferevent 等。
  • BEV_OPT_THREADSAFE :自动为 bufferevent 分配锁,这样就可以安全地在多个线程中使用 bufferevent。
  • BEV_OPT_DEFER_CALLBACKS :设置这个标志时, bufferevent 延迟所有回调
  • BEV_OPT_UNLOCK_CALLBACKS :默认情况下,如果设置 bufferevent 为线程安全的,则 bufferevent 会在调用用户提供的回调时进行锁定。设置这个选项会让 libevent 在执行回调的时候不进行锁定。

2.bufferevent接口

2.1基于套接字的bufferevent

创建套接字

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

参数:

  • base:事件集合
  • fd:套接字文件描述符
  • options:bufferevent的选项掩码
2.2启动链接
int bufferevent_socket_connect(struct bufferevent *bev,struct sockaddr *address, int addrlen);

address 和 addrlen 参数跟标准调用 connect()的参数相同。如果还没有为 bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的。

如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知 libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。

连接完成之前可以向输出缓冲区添加数据。

实例

#include <event2/event.h>
#include <event2/bufferevent.h>
#include <sys/socket.h>
#include <string.h>
void eventcb(struct bufferevent *bev, short events, void *ptr)
{
    if (events & BEV_EVENT_CONNECTED) {
         /* connect to 127.0.0.1:8080. */
    } else if (events & BEV_EVENT_ERROR) {//发生错误
    }
}

int main_loop(void)
{
    struct event_base *base;
    struct bufferevent *bev;
    struct sockaddr_in sin;
    base = event_base_new();
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
    sin.sin_port = htons(8080); /* Port 8080 */

    bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
    bufferevent_setcb(bev, NULL, NULL, eventcb, NULL);
    if (bufferevent_socket_connect(bev,
        (struct sockaddr *)&sin, sizeof(sin)) < 0) {
        /* Error starting connection */
        bufferevent_free(bev);
        return -1;
    }
    event_base_dispatch(base);	//x
    return 0;
}
2.3释放bufferevent操作

void bufferevent_free(struct bufferevent *bev);

这个函数释放 bufferevent。bufferevent 内部具有引用计数,所以,如果释放 时还有未决的延迟回调,则在回调完成之前 bufferevent 不会被删除。

2.4操作回调
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
typedef void (*bufferevent_event_cb)(struct bufferevent *bev,short events, void *ctx);

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

void bufferevent_getcb(struct bufferevent *bufev,
    bufferevent_data_cb *readcb_ptr,
    bufferevent_data_cb *writecb_ptr,
    bufferevent_event_cb *eventcb_ptr,
    void **cbarg_ptr);
  • bufferevent_setcb()函数修改 bufferevent 的一个或者多个回调 。readcb、writecb和eventcb函数将分别在已经读取足够的数据 、已经写入足够的数据 ,或者发生错误时被调用 。
  • 每个回调函数的第一个参数都是发生了事件的bufferevent ,最后一个参数都是调用bufferevent_setcb()时用户提供的 cbarg 参数:可以通过它向回调传递数据。事件回调 的 events 参数是一个表示事件标志的位掩码
  • 要禁用回调,传递 NULL 而不是回调函数 。

3.操作bufferevent中的数据

3.1获取evbuffer
struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);

这两个函数提供了非常强大的基础 :它们分别返回输入和输出缓冲区 。关于可以对 evbuffer 类型进行的所有操作的完整信息。

如果写入操作因为数据量太少而停止(或者读取操作因为太多数据而停止 ),则向输出缓冲区添加数据(或者从输入缓冲区移除数据)将自动重启操作。

3.2向bufferevent的输出缓冲区添加数据
int bufferevent_write(struct bufferevent *bufev,
    const void *data, size_t size);

int bufferevent_write_buffer(struct bufferevent *bufev,
    struct evbuffer *buf);
  • bufferevent_write()将内存中从 data 处开始的size 字节数据添加到输出缓冲区的末尾
  • bufferevent_write_buffer()移除 buf 的所有内容,将其放置到输出缓冲区的末尾。成功时这些函数都返回 0,发生错误时则返回-1。
3.2从bufferevent的输入缓冲区移除数据
size_t bufferevent_read(struct bufferevent *bufev, 
                        void *data, size_t size);

int bufferevent_read_buffer(struct bufferevent *bufev,
    struct evbuffer *buf);
  • bufferevent_read()至多从输入缓冲区移除 size 字节的数据,将其存储到内存中 data 处。函数返回实际移除的字节数。
  • bufferevent_read_buffer()函数抽空输入缓冲区的所有内容,将其放置到 buf 中,成功时返回0,失败时返回 -1。

实例

void read_callback_uppercase(struct bufferevent *bev, void *ctx)
{
        char tmp[128];
        size_t n;
        int i;
        while (1) {
                n = bufferevent_read(bev, tmp, sizeof(tmp));
                if (n <= 0)
                        break; /* No more data. */
                for (i=0; i<n; ++i)
                        tmp[i] = toupper(tmp[i]);
                bufferevent_write(bev, tmp, n);
        }
}

struct proxy_info {
        struct bufferevent *other_bev;
};

void read_callback_proxy(struct bufferevent *bev, void *ctx)
{
        struct proxy_info *inf = ctx;

        bufferevent_read_buffer(bev,
            bufferevent_get_output(inf->other_bev));
}

struct count {
        unsigned long last_fib[2];
};

void write_callback_fibonacci(struct bufferevent *bev, void *ctx)
{
        struct count *c = ctx;
        struct evbuffer *tmp = evbuffer_new();
        while (evbuffer_get_length(tmp) < 1024) {
                 unsigned long next = c->last_fib[0] + c->last_fib[1];
                 c->last_fib[0] = c->last_fib[1];
                 c->last_fib[1] = next;

                 evbuffer_add_printf(tmp, "%lu", next);
        }
        bufferevent_write_buffer(bev, tmp);
        evbuffer_free(tmp);
}
3.3 bufferevent的清空操作
int bufferevent_flush(struct bufferevent *bufev,short iotype, enum bufferevent_flush_mode state); 
  • 清空 bufferevent 要求 bufferevent 强制从底层传输端口读取或者写入尽可能多的数据 ,而忽略其他可能保持数据不被写入的限制条件。函数的细节功能依赖于 bufferevent 的具体类型。
  • iotype 参数应该是 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE,用于指示应该处 理读取、写入,还是二者都处理。
  • state 参数可以是 BEV_NORMAL、BEV_FLUSH 或者 BEV_FINISHED。BEV_FINISHED 指示应该告知另一端,没有更多数据需要发送了; 而 BEV_NORMAL 和 BEV_FLUSH 的区别依赖于具体的 bufferevent 类型。

4.数据封装evBuffer

libevent 的 evbuffer 实现了为向后面添加数据和从前面移除数据而优化的字节队列。

evbuffer 用于处理缓冲网络 IO 的“缓冲”部分。它不提供调度 IO 或者当 IO 就绪时触发IO 的功能:这是 bufferevent 的工作。

4.1创建释放evbufffer
struct evbuffer *evbuffer_new(void);

void evbuffer_free(struct evbuffer *buf);
  • evbuffer_new创建一个新分配的evbuffer
  • evbuffer_free释放buf对应的evbuffer
4.2evbuffer与线程安全
int evbuffer_enable_locking(struct evbuffer *buf, void *lock);

void evbuffer_lock(struct evbuffer *buf);

void evbuffer_unlock(struct evbuffer *buf);
  • 默认情况下,在多个线程中同时访问evbuffer 是不安全的。如果需要这样的访问,可以调 用 evbuffer_enable_locking() 。如果 lock 参数为 NULL , libevent 会使用evthread_set_lock_creation_callback提供的锁创建函数创建一个锁 。否则,libevent 将 lock 参数用作锁。
  • evbuffer_lock()和 evbuffer_unlock()函数分别请求和释放evbuffer 上的锁。可以使用这两个 函数让一系列操作是原子的。如果 evbuffer 没有启用锁,这两个函数不做任何操作。
4.3检查evbuffer
size_t evbuffer_get_length(const struct evbuffer *buf);
  • 该函数返回 evbuffer 存储的字节数
4.4向evbuffer中添加数据
int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);
  • 这个函数添加 data 处的 datalen 字节到 buf 的末尾
int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)
int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap);
  • 这些函数添加格式化的数据到buf末尾。格式参数和其他参数的处理分别与 C 库函数 printf 和 vprintf 相同。函数返回添加的字节数。
int evbuffer_expand(struct evbuffer *buf, size_t datlen);
  • 这个函数修改缓冲区的最后一块,或者添加一个新的块,使得缓冲区足以容纳 datlen 字节, 而不需要更多的内存分配。

实例

evbuffer_add(buf, "Hello world 2.0.1", 17);


evbuffer_add_printf(buf, "Hello %s %d.%d.%d", "world", 2, 0, 1);
4.5evbuffer数据移动

为提高效率,libevent 具有将数据从一个evbuffer移动到另一个的优化函数。

int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src);


int evbuffer_remove_buffer(struct evbuffer *src, 
                    struct evbuffer *dst,
                    size_t datlen);
  • evbuffer_add_buffer()将 src 中的所有数据移动到 dst 末尾,成功时返回0,失败时返回-1。
  • evbuffer_remove_buffer()函数从 src 中移动 datlen 字节到 dst 末尾,尽量少进行复制。如果字节数小于 datlen,所有字节被移动。函数返回移动的字节数。

5.链接监听器evconnlistener

evconnlistener 机制提供了监听和接受 TCP 连接的方法。

5.1创建和释放evconnlistener
struct evconnlistener *
evconnlistener_new(struct event_base *base,
    evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
    evutil_socket_t fd);
    

struct evconnlistener *
evconnlistener_new_bind(struct event_base *base,
    evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
    const struct sockaddr *sa, int socklen);
    
释放监听器
void evconnlistener_free(struct evconnlistener *lev);
  • 两个evconnlistener_new*()函数都分配和返回一个新的连接监听器对象。连接监听器使用 event_base 来得知什么时候在给定的监听套接字上有新的 TCP 连接。新连接到达时,监听 器调用你给出的回调函数

参数说明

  • base参数都是监听器用于监听连接的 event_base。
  • cb是收到新连接时要调用的回调函数,如果cb为NULL,则监听器是禁用的
  • ptr指针将参数传递给回调函数。
  • flags参数控制回调函数的行为。
  • backlog是任何时刻网络栈允许处于还未接受状态的最大未决连接数。

注意:两个函数的不同在于如何建立监听套接字

  • evconnlistener_new()函数假定已经将套接字绑定到要监听的端口,然后通过 fd 传入这个套接字。
  • 如果要 libevent 分配和绑定套接字,可以调用 evconnlistener_new_bind() ,传输要绑定到的地址和地址长度。
  • 要释放连接监听器,调用 evconnlistener_free()。

flags参数

  • LEV_OPT_LEAVE_SOCKETS_BLOCKING:默认情况下,连接监听器接收新套接字后,会将其设置为非阻塞的,以便将其用于 libevent。
  • LEV_OPT_CLOSE_ON_FREE:如果设置了这个选项,释放连接监听器会关闭底层套接字。
  • LEV_OPT_CLOSE_ON_EXEC:如果设置了这个选项,连接监听器会为底层套接字设置 close-on-exec 标志。
  • LEV_OPT_REUSEABLE:某些平台在默认情况下 ,关闭某监听套接字后 ,要过一会儿其他套接字才可以绑定到同一个 端口。设置这个标志会让 libevent 标记套接字是可重用的,这样一旦关闭,可以立即打开其 他套接字,在相同端口进行监听。
  • LEV_OPT_THREADSAFE:为监听器分配锁,这样就可以在多个线程中安全地使用了。
5.2链接监听器回调
typedef void (*evconnlistener_cb)(struct evconnlistener *listener,
    evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr);
  • 接收到新连接会调用提供的回调函数 。
  • listener 参数是接收连接的连接监听器 。
  • sock参数是 新接收的套接字。
  • addr和len参数是接收连接的地址和地址长度。
  • ptr是调 用 evconnlistener_new() 时用户提供的指针。
5.3启用和禁用evconnlistener
int evconnlistener_disable(struct evconnlistener *lev);

int evconnlistener_enable(struct evconnlistener *lev);
  • evconnlistener_disable禁用监听器
  • evconnlistener_enable重新启动监听器
5.4调整evconnlistener的回调函数
void evconnlistener_set_cb(struct evconnlistener *lev,
    evconnlistener_cb cb, void *arg);

调整evconnlistener的回调函数和回调函数的参数

5.5获取错误信息
typedef void (*evconnlistener_errorcb)(struct evconnlistener *lis, void *ptr);


void evconnlistener_set_error_cb(struct evconnlistener *lev,
    evconnlistener_errorcb errorcb);
  • 如果使用evconnlistener_set_error_cb()为监听器设置了错误回调函数,则监听器发生错误 时回调函数就会被调用。
  • 第一个参数是监听器
  • 第二个参数是调用 evconnlistener_new() 时传入的 ptr。

6.libevent常用设置

6.1日志消息回调设置

libevent 可以记录内部错误和警告。如果编译进日志支持,还会记录调试信息。默认配置下
这些信息被写到stderr。通过提供定制的日志函数可以覆盖默认行为。

#define EVENT_LOG_DEBUG 0
#define EVENT_LOG_MSG   1
#define EVENT_LOG_WARN  2
#define EVENT_LOG_ERR   3

/* Deprecated; see note at the end of this section */
#define _EVENT_LOG_DEBUG EVENT_LOG_DEBUG
#define _EVENT_LOG_MSG   EVENT_LOG_MSG
#define _EVENT_LOG_WARN  EVENT_LOG_WARN
#define _EVENT_LOG_ERR   EVENT_LOG_ERR


typedef void (*event_log_cb)(int severity, const char *msg);

void event_set_log_callback(event_log_cb cb);

配置实例

#include <event2/event.h>
#include <stdio.h>

static void discard_cb(int severity, const char *msg)
{
    /* do nothing */
}

static void write_to_file_cb(int severity, const char *msg)
{
    const char *s;
    if (!logfile)
        return;
    switch (severity) {
        case _EVENT_LOG_DEBUG: s = "debug"; break;
        case _EVENT_LOG_MSG:   s = "msg";   break;
        case _EVENT_LOG_WARN:  s = "warn";  break;
        case _EVENT_LOG_ERR:   s = "error"; break;
        default:               s = "?";     break; 
    }
    fprintf(logfile, "[%s] %s\n", s, msg);
}


void suppress_logging(void)
{
    event_set_log_callback(discard_cb);
}


static FILE *logfile = NULL;
void set_logfile(FILE *f)
{
    logfile = f;
    event_set_log_callback(write_to_file_cb);
}

在用户提供的event_log_cb 回调函数中调用libevent 函数是不安全的。

6.2内存管理回调

默认情况下,libevent 使用C 库的内存管理函数在堆上分配内存。

通过提供malloc、realloc和free 的替代函数,可以让libevent 使用其他的内存管理器。

如果需要自定义内存管理函数,需要进行下面的操作

void event_set_mem_functions(void *(*malloc_fn)(size_t sz),
                             void *(*realloc_fn)(void *ptr, size_t sz),
                             void (*free_fn)(void *ptr));

实例

union alignment {
    size_t sz;
    void *ptr;
    double dbl;
};

#define ALIGNMENT sizeof(union alignment)

#define OUTPTR(ptr) (((char*)ptr)+ALIGNMENT)
#define INPTR(ptr) (((char*)ptr)-ALIGNMENT)

static size_t total_allocated = 0;
static void *replacement_malloc(size_t sz)
{
    void *chunk = malloc(sz + ALIGNMENT);
    if (!chunk) return chunk;
    total_allocated += sz;
    *(size_t*)chunk = sz;
    return OUTPTR(chunk);
}
static void *replacement_realloc(void *ptr, size_t sz)
{
    size_t old_size = 0;
    if (ptr) {
        ptr = INPTR(ptr);
        old_size = *(size_t*)ptr;
    }
    ptr = realloc(ptr, sz + ALIGNMENT);
    if (!ptr)
        return NULL;
    *(size_t*)ptr = sz;
    total_allocated = total_allocated - old_size + sz;
    return OUTPTR(ptr);
}
static void replacement_free(void *ptr)
{
    ptr = INPTR(ptr);
    total_allocated -= *(size_t*)ptr;
    free(ptr);
}
void start_counting_bytes(void)
{
    event_set_mem_functions(replacement_malloc,
                            replacement_realloc,
                            replacement_free);
}

注意

  • 替换内存管理函数影响libevent 随后的所有分配、调整大小和释放内存操作。所以,必
    须保证在调用任何其他libevent 函数之前进行替换。否则,libevent 可能用你的free 函
    数释放用C 库的malloc 分配的内存。
  • 自定义的malloc和realloc函数返回的内存块应该和C标准库一样进行内存对齐。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

影中人lx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值