(十七)bufferevent的管理

本文深入解析libevent中的bufferevent结构体,重点介绍如何进行自动管理缓冲区,包括创建bufferevent、设置读写水位线、注册事件、调整优先级等功能。通过理解水位线逻辑,掌握 bufferevent 在事件触发及回调过程中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

在上节中,我们将evbuffers剩余的内容,比如读/写操作进行了剖析。接下来,我们将对bufferevent进行分析,它主要实现了对缓冲区的自动管理。
在本节中,我们先介绍相关结构体以及一些管理操作,相关代码在evbuffer.c以及event.h文件中。

struct bufferevent

该结构体用于管理bufferevent。定义如下:

struct bufferevent {
    struct event_base *ev_base;   //与缓冲区相关联的event_base

    struct event ev_read;  //缓冲区读事件
    struct event ev_write;  //缓冲区写事件

    struct evbuffer *input;    //输入缓冲区
    struct evbuffer *output;  //输出缓冲区

    struct event_watermark wm_read;  //读水位
    struct event_watermark wm_write; //写水位

    evbuffercb readcb;   //缓冲区读回调函数
    evbuffercb writecb;  //缓冲区写回调函数
    everrorcb errorcb;    //发生错误的回调函数
    void *cbarg;        //回调函数的参数

    int timeout_read;   /* in seconds */
    int timeout_write;  /* in seconds */

    short enabled;  //当前启用的事件/* events that are currently enabled */
};

里面又有一个陌生的结构体,struct event_watermark,它也在event.h中定义:

struct event_watermark {
        size_t low;   //低水位   
        size_t high;  //高水位
}

它之所以可以自动管理缓冲区就是因为水位以及相应回调函数的设置。一共有4个水位,分别是读取低水位、读取高水位、写入低水位、写入高水位,它们的作用分别如下:

  1. 读取低水位:当读取操作使输入缓冲区的数据大小超过或等于该水位时,就会调用回调函数。而读取低水位的默认值是0,所以每一次进行读取操作都会导致回调函数被调用。
  2. 读取高水位:它相当于一个警戒线,当输入缓冲区的数据量达到该水位,则停止数据的读取,直到水位回退。它的默认值是无限。
  3. 写入低水位:写入操作使得输出缓冲区的数据量低于或等于该水位时,会调用相关的回调函数。默认值是0,所以只有当输出缓冲区为空时才会调用回调函数。
  4. 写入高水位:bufferevent没有直接使用这个水位。它在bufferevent用作另外一个bufferevent的底层传输端口时有特殊意义。

接下来了解这些基本信息,我们先来看看初始化操作。

新建一个bufferevent

struct bufferevent *
bufferevent_new(int fd, evbuffercb readcb, evbuffercb writecb,
    everrorcb errorcb, void *cbarg)
{
    struct bufferevent *bufev;
    //申请内存
    if ((bufev = calloc(1, sizeof(struct bufferevent))) == NULL)
        return (NULL);
    //初始化输入缓冲区
    if ((bufev->input = evbuffer_new()) == NULL) {
        free(bufev);
        return (NULL);
    }
    //初始化输出缓冲区
    if ((bufev->output = evbuffer_new()) == NULL) {
        evbuffer_free(bufev->input);
        free(bufev);
        return (NULL);
    }
    /* 初始化缓冲区读/写事件(设置fd、事件类型、回调函数、参数等成员值) */
    event_set(&bufev->ev_read, fd, EV_READ, bufferevent_readcb, bufev);
    event_set(&bufev->ev_write, fd, EV_WRITE, bufferevent_writecb, bufev);
    //设置bufferevent中的回调函数及参数
    bufferevent_setcb(bufev, readcb, writecb, errorcb, cbarg);

    /*
     * Set to EV_WRITE so that using bufferevent_write is going to
     * trigger a callback.  Reading needs to be explicitly enabled
     * because otherwise no data will be available.
     */
    //自动启用写事件,读事件需要有数据可读时才被启用
    bufev->enabled = EV_WRITE;

    return (bufev);
}

该函数无需多说什么,而对应的bufferevent_free主要就是几个释放资源的操作,如下:

/* Closing the file descriptor is the responsibility of the caller */

void
bufferevent_free(struct bufferevent *bufev)
{
    event_del(&bufev->ev_read);
    event_del(&bufev->ev_write);

    evbuffer_free(bufev->input);
    evbuffer_free(bufev->output);

    free(bufev);
}

接下来便是一些设置相关成员的函数,比如注册到event_base还有设置事件优先级、回调函数等。

将读/写事件注册到event_base上

这个操作主要由bufferevent_base_set完成:

int
bufferevent_base_set(struct event_base *base, struct bufferevent *bufev)
{
    int res;

    bufev->ev_base = base;

    res = event_base_set(base, &bufev->ev_read);
    if (res == -1)
        return (res);

    res = event_base_set(base, &bufev->ev_write);
    return (res);
}

如果对event_base_set函数的作用有所疑惑,建议重新翻到第6小节看看。

设置水位线

/*
 * Sets the water marks
 */
void
bufferevent_setwatermark(struct bufferevent *bufev, short events,
    size_t lowmark, size_t highmark)
{
    /* 根据事件的类型来设置相应的水位 */
    if (events & EV_READ) {
        bufev->wm_read.low = lowmark;
        bufev->wm_read.high = highmark;
    }

    if (events & EV_WRITE) {
        bufev->wm_write.low = lowmark;
        bufev->wm_write.high = highmark;
    }

    /* If the watermarks changed then see if we should call read again */
    bufferevent_read_pressure_cb(bufev->input,
        0, EVBUFFER_LENGTH(bufev->input), bufev);
}

若水位发生了变化,可能会导致读事件再次发生。

void
bufferevent_read_pressure_cb(struct evbuffer *buf, size_t old, size_t now,  
    void *arg) {
    struct bufferevent *bufev = arg;
    /*
     * If we are below the watermark then reschedule reading if it's
     * still enabled.
     */
    //如果高水位线为0,或者当前缓冲区大小小于高水位线并且当读操作启用时,证明还可以进行读,则重新调度读事件
    if (bufev->wm_read.high == 0 || now < bufev->wm_read.high) {  
        evbuffer_setcb(buf, NULL, NULL); //把该缓冲区的回调函数设为空

        if (bufev->enabled & EV_READ)
            bufferevent_add(&bufev->ev_read, bufev->timeout_read);  //让这个事件过timeout_read再读
    }                                       
}

设置优先级

int
bufferevent_priority_set(struct bufferevent *bufev, int priority)
{
    if (event_priority_set(&bufev->ev_read, priority) == -1)   
        return (-1);
    if (event_priority_set(&bufev->ev_write, priority) == -1)
        return (-1);

    return (0);
}

里面调用的主要函数也是我们以前讲过的。

设置定时

void
bufferevent_settimeout(struct bufferevent *bufev,
    int timeout_read, int timeout_write) {
    bufev->timeout_read = timeout_read;
    bufev->timeout_write = timeout_write;

    /* 检测时间是否处于未决状态
     * 如果是,则注册该定时事件
     */
    if (event_pending(&bufev->ev_read, EV_READ, NULL))
        bufferevent_add(&bufev->ev_read, timeout_read);
    if (event_pending(&bufev->ev_write, EV_WRITE, NULL))
        bufferevent_add(&bufev->ev_write, timeout_write);
}  

注册事件

static int
bufferevent_add(struct event *ev, int timeout)
{
    struct timeval tv, *ptv = NULL;

    if (timeout) {
        evutil_timerclear(&tv); //清空tv结构体
        tv.tv_sec = timeout;
        ptv = &tv;
    }

    //注册事件到event_base上
    return (event_add(ev, ptv));  
}

注销事件

int
bufferevent_disable(struct bufferevent *bufev, short event)
{
    if (event & EV_READ) {
        if (event_del(&bufev->ev_read) == -1)
            return (-1);
    }
    if (event & EV_WRITE) {
        if (event_del(&bufev->ev_write) == -1)
            return (-1);
    }

    bufev->enabled &= ~event;
    return (0);
}

注册事件,根据事件类型并设置enabled

int
bufferevent_enable(struct bufferevent *bufev, short event)
{
    if (event & EV_READ) {
        if (bufferevent_add(&bufev->ev_read, bufev->timeout_read) == -1)
            return (-1);
    }
    if (event & EV_WRITE) {
        if (bufferevent_add(&bufev->ev_write, bufev->timeout_write) == -1)
            return (-1);
    }

    bufev->enabled |= event;
    return (0);
}

小结

本节主要介绍了bufferevent水位线的设置,以及它主要的逻辑,这个和之前讲的事件相关的有很大的联系。当事件被激活时,首先调用的是bufferevent_readcbbufferevent_writecb,然后bufferevent_readcbbufferevent_writecb中才调用用户自己设置的回调函数。
在下一节中,我们将主要分析这两个函数,以及剩余的读/写函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值